JavaRush /Blog Java /Random-FR /Qu'est-ce que Mapstruct et comment le configurer correcte...
Roman Beekeeper
Niveau 35

Qu'est-ce que Mapstruct et comment le configurer correctement pour les tests unitaires dans les applications SpringBoot

Publié dans le groupe Random-FR

Arrière-plan

Bonjour à tous, mes chers amis et lecteurs ! Avant d'écrire l'article, un peu de contexte... J'ai récemment rencontré un problème avec la bibliothèque Mapstruct , que j'ai brièvement décrit dans ma chaîne de télégrammes ici . Le problème avec le message a été résolu dans les commentaires, mon collègue du projet précédent m'a aidé. Qu'est-ce que Mapstruct et comment le configurer correctement pour les tests unitaires dans les applications SpringBoot.  Partie 1 - 1Après cela, j'ai décidé d'écrire un article sur ce sujet, mais bien sûr, nous n'adopterons pas une vision étroite et essaierons d'abord de nous mettre au courant, de comprendre ce qu'est Mapstruct et pourquoi il est nécessaire, et en utilisant un exemple réel, nous le ferons analyser la situation qui s'est produite plus tôt et comment la résoudre. Par conséquent, je recommande fortement de faire tous les calculs en parallèle avec la lecture de l'article afin de tout expérimenter en pratique. Avant de commencer, abonnez-vous à ma chaîne Telegram , j'y rassemble mes activités, j'écris des réflexions sur le développement en Java et l'informatique en général. Abonné ? Super! Eh bien, maintenant, allons-y !

Mapstruct, FAQ ?

Un générateur de code pour des mappages rapides de beans de type sécurisé. Notre première tâche est de comprendre ce qu'est Mapstruct et pourquoi nous en avons besoin. En général, vous pouvez en savoir plus sur le site officiel. Sur la page principale du site, il y a trois réponses aux questions : qu'est-ce que c'est ? Pour quoi? Comment? Nous allons essayer de faire ceci également :

Ce que c'est?

Mapstruct est une bibliothèque qui permet de mapper (mapper, en général, c'est ce qu'on dit toujours : mapper, mapper, etc.) des objets de certaines entités en objets d'autres entités en utilisant du code généré basé sur des configurations décrites via des interfaces.

Pour quoi?

Pour la plupart, nous développons des applications multicouches (une couche pour travailler avec la base de données, une couche de logique métier, une couche pour l'interaction de l'application avec le monde extérieur) et chaque couche a ses propres objets pour stocker et traiter les données. . Et ces données doivent être transférées de couche en couche en passant d’une entité à une autre. Pour ceux qui n’ont pas travaillé avec cette approche, cela peut paraître un peu compliqué. Par exemple, nous avons une entité pour la base de données Student. Lorsque les données de cette entité vont à la couche de logique métier (services), nous devons transférer les données de la classe Student vers la classe StudentModel. Ensuite, après toutes les manipulations avec la logique métier, les données doivent être diffusées à l'extérieur. Et pour cela nous avons la classe StudentDto. Bien sûr, nous devons transmettre les données de la classe StudentModel à StudentDto. Écrire à la main à chaque fois les méthodes qui seront transférées demande beaucoup de travail. De plus, il s'agit d'un code supplémentaire dans la base de code qui doit être conservé. Vous pouvez faire une erreur. Et Mapstruct génère de telles méthodes au stade de la compilation et les stocke dans les sources générées.

Comment?

Utiliser des annotations. Nous avons juste besoin de créer une annotation comportant une annotation principale Mapper qui indique à la bibliothèque que les méthodes de cette interface peuvent être utilisées pour traduire d'un objet à un autre. Comme je l'ai dit plus tôt à propos des étudiants, dans notre cas il s'agira de l'interface StudentMapper, qui disposera de plusieurs méthodes pour transférer des données d'une couche à une autre :
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;
}
Pour ces classes, nous créons un mappeur (ci-après c'est ce que nous appellerons l'interface, qui décrit ce que nous voulons transférer et où) :
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
La beauté de cette approche est que si les noms et les types de champs sont les mêmes dans différentes classes (comme dans notre cas), alors les paramètres de Mapstruct sont suffisants pour générer l'implémentation nécessaire basée sur l'interface StudentMapper au stade de la compilation, qui traduira. Alors c’est déjà devenu plus clair, non ? Allons plus loin et utilisons un exemple réel pour analyser le travail dans une application Spring Boot.

Un exemple de fonctionnement de Spring Boot et Mapstruct

La première chose dont nous avons besoin est de créer un projet Spring Boot et d'y ajouter Mapstruct. À cet égard, j'ai une organisation dans GitHub avec des modèles de référentiels et un démarrage pour Spring Boot en fait partie. Sur cette base, nous créons un nouveau projet : Qu'est-ce que Mapstruct et comment le configurer correctement pour les tests unitaires dans les applications SpringBoot.  Partie 1 - 2Ensuite, nous obtenons le projet . Oui, les amis, donnez une étoile au projet si vous l'avez trouvé utile, ainsi je saurai que je ne fais pas cela en vain. Dans ce projet, nous révélerons une situation que j'ai reçue au travail et décrite dans un post sur ma chaîne Telegram . Je vais brièvement décrire la situation pour ceux qui ne sont pas au courant : lorsque nous écrivons des tests pour les mappeurs (c'est-à-dire pour les implémentations d'interface dont nous avons parlé plus tôt), nous voulons que les tests réussissent le plus rapidement possible. L'option la plus simple avec les mappeurs consiste à utiliser l'annotation SpringBootTest lors de l'exécution du test, qui récupérera l'intégralité du ApplicationContext de l'application Spring Boot et injectera le mappeur nécessaire au test dans le test. Mais cette option demande beaucoup de ressources et prend beaucoup plus de temps, elle ne nous convient donc pas. Nous devons écrire un test unitaire qui crée simplement le mappeur souhaité et vérifie que ses méthodes fonctionnent exactement comme prévu. Pourquoi avez-vous besoin que les tests s’exécutent plus rapidement ? Si les tests prennent du temps, cela ralentit tout le processus de développement. Jusqu'à ce que les tests transmettent le nouveau code, ce code ne peut pas être considéré comme correct et ne sera pas pris en compte pour les tests, ce qui signifie qu'il ne sera pas mis en production et que le développeur n'a pas terminé le travail. Il semblerait, pourquoi écrire un test pour une bibliothèque dont le fonctionnement ne fait aucun doute ? Et pourtant, nous devons écrire un test, car nous testons dans quelle mesure nous avons correctement décrit le mappeur et s'il fait ce que nous attendons. Tout d'abord, pour faciliter notre travail, ajoutons Lombok à notre projet en ajoutant une autre dépendance à pom.xml :
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
Dans notre projet, nous devrons passer des classes modèles (qui sont utilisées pour travailler avec la logique métier) aux classes DTO, que nous utilisons pour communiquer avec le monde extérieur. Dans notre version simplifiée, nous supposerons que les champs ne changent pas et nos mappeurs seront simples. Mais si vous le souhaitez, il serait possible d'écrire un article plus détaillé sur la façon de travailler avec Mapstruct, de le configurer et de profiter de ses avantages. Mais alors, puisque cet article sera assez long. Disons que nous avons un étudiant avec une liste de conférences et de conférenciers auxquels il assiste. Créons un package modèle . Sur cette base, nous allons créer un modèle simple :
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;
}
ses conférences
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
et conférenciers
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Et créez un package dto à côté du package model :
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;
}
conférences
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
et conférenciers
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
Créons maintenant un mappeur qui traduira une collection de modèles de cours en une collection de cours DTO. La première chose à faire est d'ajouter Mapstruct au projet. Pour ce faire, nous utiliserons leur site officiel , tout y est décrit. Autrement dit, nous devons ajouter une dépendance et un plugin à notre mémoire (si vous avez des questions sur ce qu'est une mémoire, c'est parti, Article1 et Article2 ) :
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
et en mémoire dans le bloc <build/>. que nous n'avons pas encore eu :
<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>
Ensuite, créons un package mapper à côté de dto et model . Sur la base des classes que nous avons montrées précédemment, vous devrez créer cinq mappeurs supplémentaires :
  • Mapper LectureModel <-> LectureDTO
  • Liste des mappeurs<LectureModel> <-> Liste<LectureDTO>
  • Mapper LecturerModel <-> LecturerDTO
  • Liste des mappeurs<LecturerModel> <-> Liste<LecturerDTO>
  • Mappeur StudentModel <-> StudentDTO
Aller:

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);
}

ConférencierMapper

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);
}

ConférencierListeMapper

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);
}

ÉtudiantMapper

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);
}
Il convient de noter séparément que dans les mappeurs, nous faisons référence à d'autres mappeurs. Cela se fait via le champ use dans l'annotation Mapper, comme cela se fait dans StudentMapper :
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
Ici, nous utilisons deux mappeurs pour mapper correctement la liste des conférences et la liste des conférenciers. Nous devons maintenant compiler notre code et voir ce qu'il y a et comment. Cela peut être fait en utilisant la commande mvn clean compile . Mais il s'est avéré que lors de la création des implémentations Mapstruct de nos mappeurs, les implémentations du mappeur n'ont pas écrasé les champs. Pourquoi? Il s'est avéré qu'il n'était pas possible de récupérer l'annotation Data de Lombok. Et il fallait faire quelque chose... C'est pourquoi nous avons une nouvelle section dans l'article.

Lier Lombok et Mapstruct

Après quelques minutes de recherche, il s'est avéré que nous devions connecter Lombok et Mapstruct d'une certaine manière. Il y a des informations à ce sujet dans la documentation Mapstruct . Après avoir examiné l'exemple proposé par les développeurs de Mapstruct, mettons à jour notre pom.xml : Ajoutons des versions distinctes :
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
Ajoutons la dépendance manquante :
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
Et mettons à jour notre plugin de compilateur pour qu'il puisse connecter Lombok et 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>
Après cela, tout devrait s'arranger. Compilons à nouveau notre projet. Mais où pouvez-vous trouver les classes générées par Mapstruct ? Ils se trouvent dans les sources générées : ${projectDir}/target/generated-sources/annotations/ Qu'est-ce que Mapstruct et comment le configurer correctement pour les tests unitaires dans les applications SpringBoot.  Partie 1 à 3 Maintenant que nous sommes prêts à réaliser ma déception face au post de Mapstruct, essayons de créer des tests pour les mappeurs.

Nous écrivons des tests pour nos mappeurs

Je vais créer un test simple et rapide qui testerait l'un des mappeurs dans le cas où nous créons un test d'intégration et ne nous soucions pas de son temps d'exécution :

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());
   }
}
Ici, en utilisant l'annotation SpringBootTest, nous lançons l'intégralité du applicationContext et à partir de celui-ci, en utilisant l'annotation Autowired, nous extrayons la classe dont nous avons besoin pour les tests. Du point de vue de la rapidité et de la facilité de rédaction d'un test, c'est très bien. Le test réussit, tout va bien. Mais nous allons aller dans l'autre sens et écrire un test unitaire pour un mappeur, par exemple 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());
   }
}
Étant donné que les implémentations générées par Mapstruct sont dans la même classe que notre projet, nous pouvons facilement les utiliser dans nos tests. Tout a l'air bien - pas d'annotations, nous créons la classe dont nous avons besoin de la manière la plus simple et c'est tout. Mais lorsque nous lancerons le test, nous comprendrons qu'il va planter et qu'il y aura une NullPointerException dans la console... En effet, l'implémentation du mappeur LectureListMapper ressemble à :
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;
   }
}
Si nous regardons le NPE (abréviation de NullPointerException), nous l'obtenons à partir de la variable lectureMapper , qui s'avère n'être pas initialisée. Mais dans notre implémentation, nous n’avons pas de constructeur avec lequel nous pourrions initialiser la variable. C'est exactement la raison pour laquelle Mapstruct a implémenté le mappeur de cette façon ! Au Spring, vous pouvez ajouter des beans aux classes de plusieurs manières, vous pouvez les injecter via un champ avec l'annotation Autowired, comme fait ci-dessus, ou vous pouvez les injecter via un constructeur. Je me suis retrouvé dans une situation tellement problématique au travail lorsque j'avais besoin d'optimiser le temps d'exécution des tests. J’ai pensé qu’on ne pouvait rien y faire et j’ai exprimé ma douleur sur ma chaîne Telegram. Et puis ils m'ont aidé dans les commentaires et m'ont dit qu'il était possible de personnaliser la stratégie d'injection. L'interface Mapper possède un champ injectionStrategy , qui accepte simplement le nom InjectionStrategy , qui a deux valeurs : FIELD et CONSTRUCTOR . Maintenant, sachant cela, ajoutons ce paramètre à nos mappeurs ; je vais le montrer en utilisant LectureListMapper comme exemple :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
J'ai surligné en gras la partie que j'ai ajoutée. Ajoutons cette option pour toutes les autres et recompilons le projet pour que les mappeurs soient générés avec une nouvelle ligne. Ce faisant, voyons comment l'implémentation du mappeur pour LectureListMapper a changé (surlignée en gras la partie dont nous avons besoin) :
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;
   }
}
Et maintenant, Mapstruct a implémenté l'injection de mappeur via le constructeur. C’est exactement ce que nous essayions de réaliser. Maintenant, notre test va arrêter de compiler, mettons-le à jour et obtenons :
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());
   }
}
Maintenant, si nous lançons le test, tout fonctionnera comme prévu, puisque dans LectureListMapperImpl nous passons le LectureMapper dont il a besoin... Victoire ! Ce n'est pas difficile pour vous, mais je suis content : les amis, tout est comme d'habitude, abonnez-vous à mon compte GitHub , à mon compte Telegram . Là je poste les résultats de mes activités, il y a des choses vraiment utiles) Je vous invite notamment à rejoindre le groupe de discussion de la chaîne télégramme . Il se trouve que si quelqu’un a une question technique, il peut y obtenir une réponse. Ce format est intéressant pour tout le monde, vous pouvez lire qui sait quoi et acquérir de l'expérience.

Conclusion

Dans le cadre de cet article, nous avons fait connaissance avec un produit aussi nécessaire et fréquemment utilisé que Mapstruct. Nous avons compris ce que c'est, pourquoi et comment. À l’aide d’un exemple concret, nous avons ressenti ce qui pouvait être fait et comment cela pouvait être modifié. Nous avons également regardé comment configurer l'injection des beans via le constructeur, afin qu'il soit possible de tester correctement les mappeurs. Les collègues de Mapstruct ont permis aux utilisateurs de leur produit de choisir exactement comment injecter les mappeurs, ce pour quoi nous les remercions sans aucun doute. MAIS, malgré le fait que Spring recommande d'injecter des beans via le constructeur, les gars de Mapstruct ont défini l'injection via le champ par défaut. Pourquoi donc? Pas de réponse. Je soupçonne qu'il peut y avoir des raisons que nous ne connaissons tout simplement pas, et c'est pourquoi ils ont procédé de cette façon. Et pour les découvrir, j'ai créé un ticket GitHub sur leur référentiel de produits officiel.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION