MapStruct:模拟嵌套映射器
MapStruct : mocking nested mapper
我使用 MapStruct 来映射我的实体,我正在使用 Mockito 模拟我的对象。
我想测试一个包含 mapStruct 映射的方法。
问题是嵌套映射器在我的单元测试中始终为空(在应用程序中运行良好)
这是我的映射器声明:
@Mapper(componentModel = "spring", uses = MappingUtils.class)
public interface MappingDef {
UserDto userToUserDto(User user)
}
这是我的嵌套映射器
@Mapper(componentModel = "spring")
public interface MappingUtils {
//.... other mapping methods used by userToUserDto
这是我要测试的方法:
@Service
public class SomeClass{
@Autowired
private MappingDef mappingDef;
public UserDto myMethodToTest(){
// doing some business logic here returning a user
// User user = Some Business Logic
return mappingDef.userToUserDto(user)
}
这是我的单元测试:
@RunWith(MockitoJUnitRunner.class)
public class NoteServiceTest {
@InjectMocks
private SomeClass someClass;
@Spy
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
@Spy
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
//initMocks is omitted for brevity
@test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
mappingDef
注入正确,但 mappingUtils
始终为 null
Disclamer:这不是 this question 的副本。他正在使用@Autowire,所以他正在加载 spring 上下文,所以他正在进行集成测试。我正在做单元测试,所以我不使用@Autowired
我不想做 mappingDef
和 mappingUtils
@Mock
所以我不需要在每个用例中做 when(mappingDef.userToUserDto(user)).thenReturn(userDto)
所以,试试这个:
马文:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
@ComponentScan(basePackageClasses = NoteServiceTest.class)
@Configuration
public class NoteServiceTest {
@Autowired
private SomeClass someClass;
private ConfigurableApplicationContext context;
@Before
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@After
public void springDown() {
if ( context != null ) {
context.close();
}
}
@test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
更好的方法是一直使用构造函数注入...也在SomeClass
中使用@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
..那么你不需要spring/spring 模拟你的测试用例。
强制 MapStruct 生成带有构造函数注入的实现
@Mapper(componentModel = "spring", uses = MappingUtils.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MappingDef {
UserDto userToUserDto(User user)
}
@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MappingUtils {
//.... other mapping methods used by userToUserDto
使用构造函数注入,这样你就可以用映射器构造被测试的class。
@Service
public class SomeClass{
private final MappingDef mappingDef;
@Autowired
public SomeClass(MappingDef mappingDef) {
this.mappingDef = mappingDef;
}
public UserDto myMethodToTest(){
// doing some business logic here returning a user
// User user = Some Business Logic
return mappingDef.userToUserDto(user)
}
测试 SomeClass。注意:它不是你在这里测试的映射器,所以映射器可以被模拟。
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
private SomeClass classUnderTest;
@Mock
private MappingDef mappingDef;
@Before init() {
classUnderTest = new SomeClass(mappingDef);
// defaultMockBehaviour:
when(mappingDef.userToUserDto(anyObject(User.class).thenReturn(new UserDto());
}
@test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
在真正的单元测试中,还要测试映射器。
@RunWith(MockitoJUnitRunner.class)
public class MappingDefTest {
MappingDef classUnderTest;
@Before
void before() {
// use some reflection to get an implementation
Class aClass = Class.forName( MappingDefImpl.class.getCanonicalName() );
Constructor constructor =
aClass.getConstructor(new Class[]{MappingUtils.class});
classUnderTest = (MappingDef)constructor.newInstance( Mappers.getMapper( MappingUtils.class ));
}
@Test
void test() {
// test all your mappings (null's in source, etc)..
}
如果您愿意使用 Spring 测试工具,使用 org.springframework.test.util.ReflectionTestUtils
非常简单。
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
...
// Somewhere appropriate
@Before
void before() {
ReflectionTestUtils.setField(
mappingDef,
"mappingUtils",
mappingUtils
)
}
作为 的变体,现在可以依靠 MapStruct 本身来检索实现 class,同时还可以通过正确使用泛型来避免强制转换:
Class<? extends MappingDef> mapperClass = Mappers.getMapperClass(MappingDef.class);
Constructor<? extends MappingDef> constructor = mapperClass.getConstructor(MappingUtils.class);
MappingDef mappingDef = constructor.newInstance(Mappers.getMapper(MappingUtils.class));
这甚至可以通过检查构造函数、找到它需要的所有映射器作为参数并递归解析这些映射器来完全通用。
我使用 MapStruct 来映射我的实体,我正在使用 Mockito 模拟我的对象。
我想测试一个包含 mapStruct 映射的方法。 问题是嵌套映射器在我的单元测试中始终为空(在应用程序中运行良好)
这是我的映射器声明:
@Mapper(componentModel = "spring", uses = MappingUtils.class)
public interface MappingDef {
UserDto userToUserDto(User user)
}
这是我的嵌套映射器
@Mapper(componentModel = "spring")
public interface MappingUtils {
//.... other mapping methods used by userToUserDto
这是我要测试的方法:
@Service
public class SomeClass{
@Autowired
private MappingDef mappingDef;
public UserDto myMethodToTest(){
// doing some business logic here returning a user
// User user = Some Business Logic
return mappingDef.userToUserDto(user)
}
这是我的单元测试:
@RunWith(MockitoJUnitRunner.class)
public class NoteServiceTest {
@InjectMocks
private SomeClass someClass;
@Spy
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
@Spy
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
//initMocks is omitted for brevity
@test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
mappingDef
注入正确,但 mappingUtils
始终为 null
Disclamer:这不是 this question 的副本。他正在使用@Autowire,所以他正在加载 spring 上下文,所以他正在进行集成测试。我正在做单元测试,所以我不使用@Autowired
我不想做 mappingDef
和 mappingUtils
@Mock
所以我不需要在每个用例中做 when(mappingDef.userToUserDto(user)).thenReturn(userDto)
所以,试试这个:
马文:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
@ComponentScan(basePackageClasses = NoteServiceTest.class)
@Configuration
public class NoteServiceTest {
@Autowired
private SomeClass someClass;
private ConfigurableApplicationContext context;
@Before
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@After
public void springDown() {
if ( context != null ) {
context.close();
}
}
@test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
更好的方法是一直使用构造函数注入...也在SomeClass
中使用@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
..那么你不需要spring/spring 模拟你的测试用例。
强制 MapStruct 生成带有构造函数注入的实现
@Mapper(componentModel = "spring", uses = MappingUtils.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MappingDef {
UserDto userToUserDto(User user)
}
@Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface MappingUtils {
//.... other mapping methods used by userToUserDto
使用构造函数注入,这样你就可以用映射器构造被测试的class。
@Service
public class SomeClass{
private final MappingDef mappingDef;
@Autowired
public SomeClass(MappingDef mappingDef) {
this.mappingDef = mappingDef;
}
public UserDto myMethodToTest(){
// doing some business logic here returning a user
// User user = Some Business Logic
return mappingDef.userToUserDto(user)
}
测试 SomeClass。注意:它不是你在这里测试的映射器,所以映射器可以被模拟。
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {
private SomeClass classUnderTest;
@Mock
private MappingDef mappingDef;
@Before init() {
classUnderTest = new SomeClass(mappingDef);
// defaultMockBehaviour:
when(mappingDef.userToUserDto(anyObject(User.class).thenReturn(new UserDto());
}
@test
public void someTest(){
UserDto userDto = someClass.myMethodToTest();
//and here some asserts
}
在真正的单元测试中,还要测试映射器。
@RunWith(MockitoJUnitRunner.class)
public class MappingDefTest {
MappingDef classUnderTest;
@Before
void before() {
// use some reflection to get an implementation
Class aClass = Class.forName( MappingDefImpl.class.getCanonicalName() );
Constructor constructor =
aClass.getConstructor(new Class[]{MappingUtils.class});
classUnderTest = (MappingDef)constructor.newInstance( Mappers.getMapper( MappingUtils.class ));
}
@Test
void test() {
// test all your mappings (null's in source, etc)..
}
如果您愿意使用 Spring 测试工具,使用 org.springframework.test.util.ReflectionTestUtils
非常简单。
MappingDef mappingDef = Mappers.getMapper(MappingDef.class);
MappingUtils mappingUtils = Mappers.getMapper(MappingUtils.class);
...
// Somewhere appropriate
@Before
void before() {
ReflectionTestUtils.setField(
mappingDef,
"mappingUtils",
mappingUtils
)
}
作为
Class<? extends MappingDef> mapperClass = Mappers.getMapperClass(MappingDef.class);
Constructor<? extends MappingDef> constructor = mapperClass.getConstructor(MappingUtils.class);
MappingDef mappingDef = constructor.newInstance(Mappers.getMapper(MappingUtils.class));
这甚至可以通过检查构造函数、找到它需要的所有映射器作为参数并递归解析这些映射器来完全通用。