JavaRush /Java Blog /Random-IT /Cos'è Mapstruct e come configurarlo correttamente per i t...
Roman Beekeeper
Livello 35

Cos'è Mapstruct e come configurarlo correttamente per i test unitari nelle applicazioni SpringBoot

Pubblicato nel gruppo Random-IT

Sfondo

Ciao a tutti, miei cari amici e lettori! Prima di scrivere l'articolo, un po' di background... Recentemente ho riscontrato un problema lavorando con la libreria Mapstruct , che ho brevemente descritto nel mio canale Telegram qui . Nei commenti, il problema con il post è stato risolto; il mio collega del progetto precedente mi ha aiutato in questo. Cos'è Mapstruct e come configurarlo correttamente per i test unitari nelle applicazioni SpringBoot.  Parte 1 - 1Successivamente, ho deciso di scrivere un articolo su questo argomento, ma ovviamente non avremo una visione ristretta e proveremo prima ad aggiornarci, a capire cos'è Mapstruct e perché è necessario, e utilizzando un esempio reale lo faremo analizzare la situazione che si è creata in precedenza e come risolverla. Pertanto, consiglio vivamente di eseguire tutti i calcoli parallelamente alla lettura dell'articolo per sperimentare tutto nella pratica. Prima di iniziare, iscriviti al mio canale Telegram , lì raccolgo le mie attività, scrivo pensieri sullo sviluppo in Java e sull'IT in generale. Iscritto? Grande! Bene, ora andiamo!

Mapstruct, domande frequenti?

Un generatore di codice per mappature bean veloci e indipendenti dai tipi. Il nostro primo compito è capire cos'è Mapstruct e perché ne abbiamo bisogno. In generale, puoi leggerlo sul sito ufficiale. Nella pagina principale del sito ci sono tre risposte alle domande: che cos'è? Per quello? Come? Proveremo a fare anche questo:

Cos'è?

Mapstruct è una libreria che aiuta a mappare (mappa, in generale, è quello che dicono sempre: mappa, mappa, ecc.) oggetti di alcune entità in oggetti di altre entità utilizzando codice generato basato su configurazioni descritte tramite interfacce.

Per quello?

Per la maggior parte, sviluppiamo applicazioni multistrato (uno strato per lavorare con il database, uno strato di logica aziendale, uno strato per l'interazione dell'applicazione con il mondo esterno) e ogni strato ha i propri oggetti per l'archiviazione e l'elaborazione dei dati . E questi dati devono essere trasferiti da un livello all'altro trasferendo da un'entità all'altra. Per coloro che non hanno lavorato con questo approccio, questo può sembrare un po' complicato. Ad esempio, abbiamo un'entità per il database Studenti. Quando i dati di questa entità passano al livello della logica aziendale (servizi), dobbiamo trasferire i dati dalla classe Student alla classe StudentModel. Successivamente, dopo tutte le manipolazioni con la logica aziendale, i dati devono essere rilasciati all'esterno. E per questo abbiamo la classe StudentDto. Ovviamente dobbiamo passare i dati dalla classe StudentModel a StudentDto. Scrivere a mano ogni volta che verranno trasferiti i metodi richiede molto lavoro. Inoltre si tratta di codice aggiuntivo nella codebase che deve essere mantenuto. Puoi fare un errore. E Mapstruct genera tali metodi in fase di compilazione e li memorizza in sorgenti generate.

Come?

Utilizzo delle annotazioni. Dobbiamo solo creare un'annotazione che abbia un'annotazione principale del Mapper che indichi alla libreria che i metodi in questa interfaccia possono essere utilizzati per tradurre da un oggetto a un altro. Come ho detto prima riguardo agli studenti, nel nostro caso si tratterà dell'interfaccia StudentMapper, che avrà diversi metodi per trasferire i dati da un livello all'altro:
public class Student {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentDTO {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentModel {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}
Per queste classi creiamo un mapper (di seguito è quella che chiameremo interfaccia, che descrive cosa vogliamo trasferire e dove):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
La bellezza di questo approccio è che se i nomi e i tipi di campi sono gli stessi in classi diverse (come nel nostro caso), allora le impostazioni per Mapstruct sono sufficienti per generare l'implementazione necessaria basata sull'interfaccia StudentMapper in fase di compilazione, che tradurrà. Quindi è già diventato più chiaro, giusto? Andiamo oltre e utilizziamo un esempio reale per analizzare il lavoro in un'applicazione Spring Boot.

Un esempio di funzionamento di Spring Boot e Mapstruct

La prima cosa di cui abbiamo bisogno è creare un progetto Spring Boot e aggiungervi Mapstruct. A questo proposito, ho un'organizzazione in GitHub con modelli per repository e un avvio per Spring Boot è uno di questi. Sulla base di ciò, creiamo un nuovo progetto: Cos'è Mapstruct e come configurarlo correttamente per i test unitari nelle applicazioni SpringBoot.  Parte 1 - 2Successivamente, otteniamo il project . Sì, amici, date una stella al progetto se lo avete trovato utile, così saprò che non lo sto facendo invano. In questo progetto sveleremo una situazione che ho ricevuto al lavoro e descritta in un post sul mio canale Telegram . Per chi non lo sapesse, illustrerò brevemente la situazione: quando scriviamo test per i mappatori (cioè per quelle implementazioni di interfaccia di cui abbiamo parlato prima), vogliamo che i test passino il più velocemente possibile. L'opzione più semplice con i mapper consiste nell'utilizzare l'annotazione SpringBootTest durante l'esecuzione del test, che preleverà l'intero ApplicationContext dell'applicazione Spring Boot e inietterà il mapper necessario per il test all'interno del test. Ma questa opzione richiede molte più risorse e molto più tempo, quindi non è adatta a noi. Dobbiamo scrivere uno unit test che crei semplicemente il mapper desiderato e controlli che i suoi metodi funzionino esattamente come ci aspettiamo. Perché hai bisogno che i test vengano eseguiti più velocemente? Se i test richiedono molto tempo, rallentano l’intero processo di sviluppo. Fino a quando i test non passano il nuovo codice, questo codice non può essere considerato corretto e non verrà preso per i test, il che significa che non verrà messo in produzione e il che significa che lo sviluppatore non ha completato il lavoro. Sembrerebbe, perché scrivere un test per una biblioteca il cui funzionamento è fuori dubbio? Eppure dobbiamo scrivere un test, perché stiamo testando quanto correttamente abbiamo descritto il mapper e se fa quello che ci aspettiamo. Innanzitutto, per facilitarci il lavoro, aggiungiamo Lombok al nostro progetto aggiungendo un'altra dipendenza a pom.xml:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
Nel nostro progetto, dovremo passare dalle classi modello (che vengono utilizzate per lavorare con la logica aziendale) alle classi DTO, che utilizziamo per comunicare con il mondo esterno. Nella nostra versione semplificata, assumeremo che i campi non cambino e che i nostri mappatori saranno semplici. Ma se lo si desidera, sarebbe possibile scrivere un articolo più dettagliato su come lavorare con Mapstruct, come configurarlo e come sfruttare i suoi vantaggi. Ma poi, visto che questo articolo sarà piuttosto lungo. Supponiamo di avere uno studente con un elenco di lezioni e docenti a cui frequenta. Creiamo un pacchetto modello . Sulla base di ciò, creeremo un modello semplice:
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
le sue lezioni
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
e docenti
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
E crea un pacchetto dto accanto al pacchetto modello :
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
lezioni
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
e docenti
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Ora creiamo un mappatore che tradurrà una raccolta di modelli di lezioni in una raccolta di lezioni DTO. La prima cosa da fare è aggiungere Mapstruct al progetto. Per fare ciò, utilizzeremo il loro sito ufficiale , lì è tutto descritto. Cioè, dobbiamo aggiungere una dipendenza e un plugin alla nostra memoria (se hai domande su cos'è una memoria, ecco qui, Articolo1 e Articolo2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
e nella memoria nel blocco <build/>. che non abbiamo ancora avuto:
<build>
   <plugins>
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.5.1</version>
           <configuration>
               <source>1.8</source>
               <target>1.8</target>
               <annotationProcessorPaths>
                   <path>
                       <groupId>org.mapstruct</groupId>
                       <artifactId>mapstruct-processor</artifactId>
                       <version>1.4.2.Final</version>
                   </path>
               </annotationProcessorPaths>
           </configuration>
       </plugin>
   </plugins>
</build>
Successivamente, creiamo un pacchetto mapper accanto a dto e model . In base alle classi che abbiamo mostrato in precedenza, dovrai creare altri cinque mappatori:
  • Mapper LectureModel <-> LectureDTO
  • Elenco mappatore<LectureModel> <-> Elenco<LectureDTO>
  • Mapper LecturerModel <-> LecturerDTO
  • Elenco mappatori<LecturerModel> <-> Elenco<LecturerDTO>
  • Mapper StudentModel <-> StudentDTO
Andare:

LectureMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LecturerDTO dto);
}

LectureListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LectureMapper.class)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}

LecturerMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LectureDTO dto);
}

LecturerListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LecturerModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LecturerMapper.class)
public interface LecturerListMapper {
   List<LecturerModel> toModelList(List<LecturerDTO> dloList);
   List<LecturerDTO> toDTOList(List<LecturerModel> modelList);
}

StudentMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.StudentDTO;
import com.github.romankh3.templaterepository.springboot.model.StudentModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
public interface StudentMapper {
   StudentDTO toDTO(StudentModel model);
   StudentModel toModel(StudentDTO dto);
}
Va notato separatamente che in mappatori ci riferiamo ad altri mappatori. Questo viene fatto attraverso il campo uses nell'annotazione Mapper, come avviene in StudentMapper:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Qui utilizziamo due mappatori per mappare correttamente l'elenco delle lezioni e l'elenco dei docenti. Ora dobbiamo compilare il nostro codice e vedere cosa c'è e come. Questo può essere fatto usando il comando mvn clean compile . Ma, come si è scoperto, durante la creazione delle implementazioni Mapstruct dei nostri mapper, le implementazioni del mapper non sovrascrivevano i campi. Perché? Si è scoperto che non era possibile ritirare l'annotazione dei dati da Lombok. E qualcosa andava fatto... Pertanto, abbiamo una nuova sezione nell'articolo.

Collegamento di Lombok e Mapstruct

Dopo alcuni minuti di ricerca, si è scoperto che dovevamo connettere Lombok e Mapstruct in un certo modo. Ci sono informazioni a riguardo nella documentazione di Mapstruct . Dopo aver esaminato l'esempio proposto dagli sviluppatori di Mapstruct, aggiorniamo il nostro pom.xml: aggiungiamo versioni separate:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Aggiungiamo la dipendenza mancante:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
E aggiorniamo il nostro plugin del compilatore in modo che possa connettere Lombok e Mapstruct:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.5.1</version>
   <configuration>
       <source>1.8</source>
       <target>1.8</target>
       <annotationProcessorPaths>
           <path>
               <groupId>org.mapstruct</groupId>
               <artifactId>mapstruct-processor</artifactId>
               <version>${org.mapstruct.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>${lombok.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok-mapstruct-binding</artifactId>
               <version>${lombok-mapstruct-binding.version}</version>
           </path>
       </annotationProcessorPaths>
   </configuration>
</plugin>
Dopodiché tutto dovrebbe funzionare. Compiliamo di nuovo il nostro progetto. Ma dove puoi trovare le classi generate da Mapstruct? Sono in generate-sources: ${projectDir}/target/generated-sources/annotations/ Cos'è Mapstruct e come configurarlo correttamente per i test unitari nelle applicazioni SpringBoot.  Parte 1 - 3 Ora che siamo pronti a realizzare la mia delusione per il post su Mapstruct, proviamo a creare test per i mappatori.

Scriviamo test per i nostri mappatori

Creerò un test rapido e semplice che testerà uno dei mappatori nel caso in cui stiamo creando un test di integrazione e non mi preoccuperò del tempo di completamento:

LectureMapperTest

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LectureMapperTest {

   @Autowired
   private LectureMapper mapperUnderTest;

   @Test
   void shouldProperlyMapModelToDto() {
       //given
       LectureModel model = new LectureModel();
       model.setId(11L);
       model.setName("lecture name");

       //when
       LectureDTO dto = mapperUnderTest.toDTO(model);

       //then
       Assertions.assertNotNull(dto);
       Assertions.assertEquals(model.getId(), dto.getId());
       Assertions.assertEquals(model.getName(), dto.getName());
   }

   @Test
   void shouldProperlyMapDtoToModel() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(11L);
       dto.setName("lecture name");

       //when
       LectureModel model = mapperUnderTest.toModel(dto);

       //then
       Assertions.assertNotNull(model);
       Assertions.assertEquals(dto.getId(), model.getId());
       Assertions.assertEquals(dto.getName(), model.getName());
   }
}
Qui, utilizzando l'annotazione SpringBootTest, lanciamo l'intero applicationContext e da esso, utilizzando l'annotazione Autowired, estraiamo la classe che ci serve per il test. Dal punto di vista della velocità e della facilità di scrittura del test, questo è molto buono. Il test è stato superato con successo, va tutto bene. Ma andremo dall'altra parte e scriveremo un test unitario per un mappatore, ad esempio LectureListMapper...
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl();

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
Poiché le implementazioni generate da Mapstruct sono nella stessa classe del nostro progetto, possiamo usarle facilmente nei nostri test. Sembra tutto fantastico: nessuna annotazione, creiamo la classe di cui abbiamo bisogno nel modo più semplice e basta. Ma quando eseguiremo il test, capiremo che andrà in crash e ci sarà una NullPointerException nella console... Questo perché l'implementazione del mapper LectureListMapper è simile a:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T21:46:12+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   @Autowired
   private LectureMapper lectureMapper;

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
Se guardiamo l'NPE (abbreviazione di NullPointerException), lo otteniamo dalla variabile lectureMapper , che risulta non essere inizializzata. Ma nella nostra implementazione non abbiamo un costruttore con cui inizializzare la variabile. Questo è esattamente il motivo per cui Mapstruct ha implementato il mapper in questo modo! In Spring, puoi aggiungere bean alle classi in diversi modi, puoi inserirli tramite un campo insieme all'annotazione Autowired, come fatto sopra, oppure puoi inserirli tramite un costruttore. Mi sono trovato in una situazione così problematica sul lavoro quando avevo bisogno di ottimizzare i tempi di esecuzione dei test. Pensavo che non si potesse fare nulla e ho sfogato il mio dolore sul mio canale Telegram. E poi mi hanno aiutato nei commenti e hanno detto che era possibile personalizzare la strategia di iniezione. L'interfaccia Mapper ha un campo injectionStrategy , che accetta semplicemente il nome InjectionStrategy , che ha due valori: FIELD e CONSTRUCTOR . Ora, sapendo questo, aggiungiamo questa impostazione ai nostri mapper; la mostrerò usando LectureListMapper come esempio :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
Ho evidenziato in grassetto la parte che ho aggiunto. Aggiungiamo questa opzione per tutte le altre e ricompiliamo il progetto in modo che i mapper vengano generati con una nuova riga. Fatto ciò, vediamo come è cambiata l'implementazione del mapper per LectureListMapper (evidenziata in grassetto la parte di cui abbiamo bisogno):
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T22:25:37+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   private final LectureMapper lectureMapper;

   @Autowired
   public LectureListMapperImpl(LectureMapper lectureMapper) {

       this.lectureMapper = lectureMapper;
   }

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
E ora Mapstruct ha implementato l'iniezione del mapper attraverso il costruttore. Questo è esattamente ciò che stavamo cercando di ottenere. Ora il nostro test smetterà di compilare, aggiorniamolo e otteniamo:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl(new LectureMapperImpl());

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
Ora, se eseguiamo il test, tutto funzionerà come previsto, poiché in LectureListMapperImpl superiamo il LectureMapper di cui ha bisogno... Vittoria! Per voi non è difficile, ma sono contento: amici, è tutto come al solito, iscrivetevi al mio account GitHub , al mio account Telegram . Lì pubblico i risultati delle mie attività, ci sono cose davvero utili) Ti invito in particolare a partecipare al gruppo di discussione del canale Telegram . Accade così che se qualcuno ha una domanda tecnica, può ottenere una risposta lì. Questo formato è interessante per tutti, puoi leggere chissà cosa e fare esperienza.

Conclusione

Nell'ambito di questo articolo, abbiamo conosciuto un prodotto così necessario e utilizzato di frequente come Mapstruct. Abbiamo capito di cosa si tratta, perché e come. Utilizzando un esempio reale, abbiamo capito cosa si potrebbe fare e come si potrebbe cambiare. Abbiamo anche visto come impostare l'iniezione dei bean attraverso il costruttore, in modo che sia possibile testare adeguatamente i mapper. I colleghi di Mapstruct hanno permesso agli utenti del loro prodotto di scegliere esattamente come iniettare i mapper, cosa per la quale li ringraziamo senza dubbio. MA, nonostante Spring consigli di iniettare i bean attraverso il costruttore, i ragazzi di Mapstruct hanno impostato l'iniezione attraverso il campo per impostazione predefinita. Perché? Nessuna risposta. Sospetto che ci possano essere ragioni che semplicemente non conosciamo, ed è per questo che hanno fatto in questo modo. E per scoprirlo, ho creato un numero di GitHub sul loro repository ufficiale dei prodotti.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION