Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MapStruct : mocking nested mapper

I using MapStruct to map my entities, and I'm mocking my objects using Mockito.

I want to test a method that contains a mapping with mapStruct. The problem is the nested mapper is always null in my unit tests (works well in the application)

this is my mapper declaration :

@Mapper(componentModel = "spring", uses = MappingUtils.class)
public interface MappingDef {
     UserDto userToUserDto(User user)
}

this is my nested mapper

@Mapper(componentModel = "spring")
public interface MappingUtils {
    //.... other mapping methods used by userToUserDto

this is the method that I want to test :

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

and this is my unit test :

@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 is injected correctly, but mappingUtils is always null

Disclamer : this is not a duplicate of this question. He is using @Autowire so he is loading the spring context so he is doing integration tests. I'm doing unit tests, so I dont to use @Autowired

I dont want to make mappingDef and mappingUtils @Mock so I don't need to do when(mappingDef.userToUserDto(user)).thenReturn(userDto) in each use case

like image 375
ihebiheb Avatar asked Feb 19 '19 20:02

ihebiheb


3 Answers

force MapStruct to generate implementations with constructor injection

@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

use constructor injection, so that you can construct the class under test with a mapper.

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

Test SomeClass. Note: its not the mapper that you test here, so the mapper can be mocked.

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

And in a true unit test, test the mapper as well.

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


like image 107
Sjaak Avatar answered Nov 02 '22 23:11

Sjaak


If you are willing to use Spring test util, it's fairly easy with 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
    )
}
like image 8
Lee Han Kyeol Avatar answered Nov 02 '22 22:11

Lee Han Kyeol


As a variant on Sjaak’s answer, it is now possible to rely on MapStruct itself to retrieve the implementation class while also avoiding casts by properly using generics:

Class<? extends MappingDef> mapperClass = Mappers.getMapperClass(MappingDef.class);
Constructor<? extends MappingDef> constructor = mapperClass.getConstructor(MappingUtils.class);
MappingDef mappingDef = constructor.newInstance(Mappers.getMapper(MappingUtils.class));

This could probably even be made completely generic by inspecting the constructor, finding all mappers it requires as arguments and recursively resolving those mappers.

like image 1
Didier L Avatar answered Nov 02 '22 23:11

Didier L