如何對 MapStruct 產生的映射器進行單元測試
1. 簡介
MapStruct 是一個強大且成熟的 Java 專案映射庫。它遵循「約定優於配置」的原則,使開發人員能夠以最小的投入映射大型複雜的 Java 物件。
在本教程中,我們將了解如何為依賴 MapStruct 產生的映射器的類別編寫單元測試。
2. 設定
在我們的專案中使用MapStruct的第一步是將 Maven 依賴項新增到我們的pom.xml
中:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.3</version>
</dependency>
此外,對於 Spring 測試案例,需要spring-context和spring-test模組:
<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. 測試場景準備
映射庫的一個常見用例是在與持久層互動之前發生的 DTO 到實體的轉換。
3.1. 資料對象
讓我們定義MediaDto,
它是映射過程的來源:
public class MediaDto {
private Long id;
private String title;
// setters - getters - constructors
}
3.2. 實體
我們演示的實體——換句話說,映射過程的目標——是Media
類別:
public class Media {
private Long id;
private String title;
// setters - getters - constructors
}
3.3. 映射器
定義了 DTO 和實體物件(即MediaDto
和Media
)後,讓我們來寫MediaMapper
定義:
@Mapper
public interface MediaMapper {
MediaDto toDto(Media media);
Media toEntity(MediaDto mediaDto);
}
對於本教學課程,我們不會使用巢狀映射器、映射器繼承或任何其他進階 MapStruct 功能,因為我們的重點是測試使用映射器的類別。
3.4. Spring Bean 映射器
由於我們將為非 Spring 服務以及 Spring 服務建立相同的測試案例,因此引入MediaMapper
Spring bean 至關重要。 MediaSpringMapper 擴展了MediaSpringMapper,
接口,可透過產生MediaMapper
MapStruct 映射器供 Spring 上下文使用:
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface MediaSpringMapper extends MediaMapper {
}
3.5. 被測類
最後, MediaService
利用MediaMapper
接口,使我們能夠編寫使用生成的MediaMapper
實例或模擬實例的測試用例:
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. 使用模擬映射器進行測試
在本節中,我們將示範如何編寫使用模擬映射器(而不是產生的映射器)的單元測試。第一個例子,我們使用 Mockito 中的模擬MediaMapper
實例化一個MediaService
實例:
@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());
}
為了確保測試案例能如預期運作,驗證斷言模擬方法toEntity()
已被呼叫。此外,我們驗證傳回的物件是否等於模擬MediaMapper
傳回的物件。
在為 Spring 管理的MediaService
實例編寫相同的測試案例之前,讓我們先建立一個定義MediaService
bean 的 Spring 上下文:
@Configuration
public class Config {
@Bean
public MediaService mediaService(MediaMapper mediaMapper) {
return new MediaService(mediaMapper);
}
}
現在,所有建置區塊都已到位,讓我們使用模擬的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());
}
}
確實,與非 Spring 版本相比,我們的測試案例程式碼簡潔得多。 Spring 容器負責管理 Bean 的初始化,因此程式碼簡潔。
5. 使用產生的映射器進行測試
測試使用映射器的服務的另一種方法是使用 MapStruct 庫產生的實際映射器實例,而不是模擬實例。在下面的範例中,我們將 MapStruct 介面Mappers
提供的映射器實例注入到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());
}
值得注意的是,上述案例的斷言驗證了傳回的物件屬性等於用於呼叫persistMedia()
方法的實例的屬性。
類似地,使用 Spring 產生的映射器實例進行測試需要稍微修改一下應用程式上下文。具體來說,Spring 容器需要一個可用的映射器實例,以便MediaService
實例化可行:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { Config.class, MediaSpringMapperImpl.class })
public class MediaServiceSpringGeneratedMapperTest {
最後,我們得到了一個專門使用 Spring 提供的 bean 的測試案例,而不需要任何手動實例化:
@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());
}
如預期的那樣,驗證部分與非 Spring 版本的測試案例相同,因為映射器的實作本質上是相同的。
6. 結論
在本文中,我們提供瞭如何測試使用 MapStruct 映射器的類別的實例。此外,我們還在 Spring 應用程式環境中演示了等效的測試案例。
與往常一樣,本文的源代碼可在 GitHub 上找到。