 Java, Spring and Web Development tutorials  1. Introduction
MapStruct is a powerful and sophisticated mapping library for Java projects. By following the convention-over-configuration approach, MapStruct enables developers to map large and complex Java objects with minimal effort.
In this tutorial, we’ll see how to write unit tests for classes that have dependencies on MapStruct-generated mappers.
2. Setup
The first step to use MapStruct in our project is to add the Maven dependency to our pom.xml:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
Additionally, for the Spring test cases, the spring-context and spring-test modules are required:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.2.1</version>
<scope>test</scope>
</dependency>
3. Test Scenario Preparation
A common use case for a mapping library is the DTO-to-entity transformation that happens before the interaction with the persistence layer.
3.1. DTO
Let’s define the MediaDto, which is the source of the mapping process:
public class MediaDto {
private Long id;
private String title;
// setters - getters - constructors
}
3.2. Entity
The entity for our demonstration – in other words, the target of the mapping process – is the Media class:
public class Media {
private Long id;
private String title;
// setters - getters - constructors
}
3.3. Mapper
With the DTO and entity objects defined, namely MediaDto and Media, respectively, let’s write the MediaMapper definition:
@Mapper
public interface MediaMapper {
MediaDto toDto(Media media);
Media toEntity(MediaDto mediaDto);
}
For this tutorial, we won’t utilize nested mappers, mapper inheritance, or any other advanced MapStruct features, as our focus is on testing classes that use mappers.
3.4. Spring Bean Mapper
Since we’ll create identical test cases for non-Spring and Spring services as well, the introduction of a MediaMapper Spring bean is essential. The MediaSpringMapper, which extends the MediaMapper interface, is available to the Spring context via the generated MapStruct mapper:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface MediaSpringMapper extends MediaMapper {
}
3.5. Class Under Test
Finally, the MediaService utilizes the MediaMapper interface, enabling us to write test cases that use either a generated MediaMapper instance or a mocked one:
public class MediaService {
private final MediaMapper mediaMapper;
public Media persistMedia(MediaDto mediaDto) {
Media media = mediaMapper.toEntity(mediaDto);
logger.info("Persist media: {}", media);
return media;
}
// getters - setters - constructors
}
4. Tests With Mocked Mappers
In this section, we’ll demonstrate how to write unit tests that use mocked mappers instead of the generated ones. For the first example, we instantiate a MediaService instance using a mocked MediaMapper with Mockito:
@Test
public void whenMockedMapperIsUsed_thenMockedValuesAreMapped() {
MediaMapper mockMediaMapper = mock(MediaMapper.class);
Media mockedMedia = new Media(5L, "Title 5");
when(mockMediaMapper.toEntity(any())).thenReturn(mockedMedia);
MediaService mediaService = new MediaService(mockMediaMapper);
MediaDto mediaDto = new MediaDto(1L, "title 1");
Media persisted = mediaService.persistMedia(mediaDto);
verify(mockMediaMapper).toEntity(mediaDto);
assertEquals(mockedMedia.getId(), persisted.getId());
assertEquals(mockedMedia.getTitle(), persisted.getTitle());
}
To ensure that the test case works as expected, the verifications assert that the mocked method toEntity() is invoked. Furthermore, we verify that the returned object is equal to the one returned by the mocked MediaMapper.
Before we write the identical test case for a Spring-managed MediaService instance, let’s create a Spring context that defines the MediaService bean:
@Configuration
public class Config {
@Bean
public MediaService mediaService(MediaMapper mediaMapper) {
return new MediaService(mediaMapper);
}
}
Now, with all the building blocks in place, let’s re-create the test case with a mocked MediaMapper:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Config.class)
public class MediaServiceSpringMockedMapperTest {
@Autowired
MediaService mediaService;
@MockitoBean
MediaSpringMapper mockMediaMapper;
@Test
public void whenMockedSpringMapperIsUsed_thenMockedValuesAreMapped() {
Media mockedMedia = new Media(12L, "title 12");
when(mockMediaMapper.toEntity(ArgumentMatchers.any())).thenReturn(mockedMedia);
MediaDto mediaDto = new MediaDto(1L, "title 1");
Media persisted = mediaService.persistMedia(mediaDto);
verify(mockMediaMapper).toEntity(mediaDto);
assertEquals(mockedMedia.getId(), persisted.getId());
assertEquals(mockedMedia.getTitle(), persisted.getTitle());
}
}
Indeed, our test case code is less verbose compared to the non-Spring version. The Spring container manages the initialization of the managed beans, hence the code brevity.
5. Tests With Generated Mappers
An alternative approach to testing a service that uses a mapper is to utilize an actual mapper instance generated by the MapStruct library, rather than a mock. In the following example, we inject the mapper instance provided by the MapStruct interface Mappers into the MediaService:
@Test
public void whenGeneratedMapperIsUsed_thenActualValuesAreMapped() {
MediaService mediaService = new MediaService(Mappers.getMapper(MediaMapper.class));
MediaDto mediaDto = new MediaDto(1L, "title 1");
Media persisted = mediaService.persistMedia(mediaDto);
assertEquals(mediaDto.getId(), persisted.getId());
assertEquals(mediaDto.getTitle(), persisted.getTitle());
}
Notably, the assertions of the above case verify that the returned object properties are equal to the properties of the instance used to invoke the persistMedia() method.
Similarly, the Spring way of testing with generated mapper instances requires a slightly modified application context. Specifically, the Spring container needs a mapper instance to be available so that the MediaService instantiation is feasible:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { Config.class, MediaSpringMapperImpl.class })
public class MediaServiceSpringGeneratedMapperTest {
Finally, we end up with a test case that exclusively uses Spring-provided beans without the need for any manual instantiation:
@Test
public void whenGeneratedSpringMapperIsUsed_thenActualValuesAreMapped() {
MediaDto mediaDto = new MediaDto(1L, "title 1");
Media persisted = mediaSpringService.persistMedia(mediaDto);
assertEquals(mediaDto.getId(), persisted.getId());
assertEquals(mediaDto.getTitle(), persisted.getTitle());
}
As expected, the verification section is identical to the non-Spring version of the test case since the mapper implementation is essentially the same.
6. Conclusion
In this article, we provided practical examples of how to test classes that utilize MapStruct mappers. Furthermore, we demonstrated the equivalent test cases in the context of a Spring application.
As always, the source code for this article is available over on GitHub. The post How to Unit Test MapStruct Generated Mappers first appeared on Baeldung.
Content mobilized by FeedBlitz RSS Services, the premium FeedBurner alternative. |