【问题标题】:MapStruct : mocking nested mapperMapStruct:模拟嵌套映射器
【发布时间】:2019-07-13 10:54:31
【问题描述】:

我使用 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 始终为空

Disclamer:这不是this question 的副本。他正在使用@Autowire,所以他正在加载弹簧上下文,所以他正在做集成测试。我正在做单元测试,所以我不使用@Autowired

我不想让mappingDefmappingUtils @Mock 所以我不需要在每个用例中都做when(mappingDef.userToUserDto(user)).thenReturn(userDto)

【问题讨论】:

  • 您使用的是什么版本的 mapstruct?最新版本为 Spring 提供了构造函数注入,因此您可以模拟每个嵌入式 bean 并创建实例。
  • 我使用的是 1.2.0 版。我没有看到版本 1.3.0 变得稳定(就在上周)。我会尝试升级。你有一个关于如何做的例子吗?我在文档中需要添加 InjectionStrategy.CONSTRUCTOR,但我不确定我是否理解得很好。我怎样才能用我的用例做到这一点?
  • 在 CDI 中,这意味着 MapStruct 生成一个将其他映射器作为参数的构造函数。我对Spring不太熟悉..但我想它的工作原理是一样的。我个人会选择一个在测试中为你执行注入的库。我认为我们在 MapStruct 联合中做了类似的事情 ..

标签: java unit-testing mockito mapstruct


【解决方案1】:

如果您愿意使用 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
    )
}

【讨论】:

    【解决方案2】:

    强制 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
    

    使用构造函数注入,以便您可以使用映射器构造被测类。

    @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).. 
      }
    
    
    

    【讨论】:

    • 谢谢。但是,如果可以监视 mappingDef 而不是模拟它,那就太好了。它使我无法编写所有 when...thenReturn 语句
    • 有可能,但我不建议这样做。看看这个stackoverflow.com/questions/12827580/…..
    • 我知道不推荐这样做,但我几乎总是使用 mapStruct 中的默认映射。所以就像我正在测试 mapStruct 库一样。监视它会为我节省太多时间。
    【解决方案3】:

    所以,试试这个:

    马文:

          <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).. 然后你就不需要在你的测试用例中使用弹簧/弹簧模拟了。

    【讨论】:

    • 但是在这里你正在做集成测试,你正在使用 @Autowire 加载 spring contxt。来自spring documentation:Spring 框架为 spring-test 模块中的集成测试提供了一流的支持。你能给我一个使用构造函数注入策略的例子吗?我不确定是否理解它
    • 有时有点不清楚集成测试是否从这里开始,单元测试是否结束(也是品味问题)。但我会添加另一个答案
    猜你喜欢
    • 2023-03-29
    • 2020-11-16
    • 1970-01-01
    • 2018-09-15
    • 1970-01-01
    • 2016-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多