How to mock mapstruct mapper output in a Spring Service?

Viewed 468

I have a spring service (MyService) that uses a mapstruct mapper (CustomMapstructMapper) :

@Service
public class MyService {

    private final ClassA classA;
    private final ClassB classB;
    /*
    other private fields declarations...
    */

    private CustomMapstructMapper customMapstructMapper = Mappers.getMapper(CustomMapstructMapper.class);

    //MyService constructor

    public MyService(final ClassA classA, etc...) {

        this.classA = classA;
        //etc...

    }

    public ServiceOutput mainMethod(some parameters) {

        //some business logic


        MyMapperOutput myMapperOutput = customMapstructMapper.map(MapperParameter parameter);

        ServiceOutput serviceOutput = some business logic with myMapperOutput;

        return serviceOutput;

    }

} 

I want to unit test MyService (I am using Junit 5) and mock my CustomMapstructMapper output without calling the real mapper during the test execution. I already have another test class that specificly test all the custom mappings in CustomMapstructMapper.

So I have this test class :

@ExtendWith(MockitoExtension.class)
class MyServiceTest {

    @InjectMocks
    private MyService myService;

    @Mock
    private CustomMapstructMapper customMapstructMapper;

    @Mock
    private MyMapperOutput myMapperOutput;

    @Test
    void testMyService() {
        /*
        Some mocks creation ..
        */

        Mockito.when(myMapperOutput.getSomeField()).thenReturn("Some value");
        Mockito.when(customMapstructMapper.map(Mockito.any(MapperParameter.class))).thenReturn(myMapperOutput);

        ServiceOutput serviceOutput = myService.mainMethod(some parameters);

        /*
        Some assertions on serviceOutput
        */

    }

} 

When I run my test, the implementation of my mapper customMapstructMapperImpl is called in MyService, not my mock. A NullPointerException is thrown in my mapper because some fields are not initiated. I have tried to create a mock with the implementation of my mapstruct mapper :

@Mock private CustomMapstructMapperImpl customMapstructMapper;

but I get the same result.

What am I doing wrong?

3 Answers

The code below works correctly. So maybe something else happened. Your code example is not quite complete.


@ExtendWith(MockitoExtension.class)
public class TestForService {

    @InjectMocks
    private Service service;

    @Mock
    private Mapper mapper;

    @Test
    public void test_service() {
        System.out.println("test_service start");
        Mockito.when(mapper.doSomeTh()).thenReturn("mock mapper doSomeTh");
        String result = service.service();
        System.out.println("test_service end, result: " + result);
    }

    static class Service {
        private Mapper mapper = Mapper.getMapper();

        public String service() {
            System.out.println("Service.service");
            return mapper.doSomeTh();
        }

        public void setMapper(Mapper mapper) {
            this.mapper = mapper;
            System.out.println("Service.setMapper: " + mapper);
        }
    }

    static class Mapper {

        public String doSomeTh() {
            System.out.println("Mapper.doSomeTh");
            return "mapper end";
        }

        public static Mapper getMapper() {
            System.out.println("Mapper.getMapper");
            return null;
        }
    }
}

result:

Mapper.getMapper
Service.setMapper: mapper
test_service start
Service.service
test_service end, result: mock mapper doSomeTh

A simpler way here: Just add a package-private setter method into your MyService

// for testing only
void CustomMapstructMapper setCustomMapstructMapper(CustomMapstructMapper mapper) {
    this.customMapstructMapper = mapper;
}

and inject your mocked CustomMapstructMapper in test code

@Test
void testMyService() {
    /*
    Some mocks creation ..
    */

    Mockito.when(myMapperOutput.getSomeField()).thenReturn("Some value");
    Mockito.when(customMapstructMapper.map(Mockito.any(MapperParameter.class))).thenReturn(myMapperOutput);

    myService.setCustomMapstructMapper(customMapstructMapper); // inject your mocked customMapstructMapper here

    ServiceOutput serviceOutput = myService.mainMethod(some parameters);

    /*
    Some assertions on serviceOutput
    */

}

You're not taking advantage of the spring framework and mapstruct support for it.

in your service change:

private CustomMapstructMapper customMapstructMapper = Mappers.getMapper(CustomMapstructMapper.class);

into

@Autowired
private CustomMapstructMapper customMapstructMapper;

If you don't have it yet at your mapper use

@Mapper( componentModel = "spring" )

This will cause the generated mapper to have the @Component annotation from the spring framework, and it becomes possible to auto-wire this.

After making these changes your method of supplying a mock for testing should work.

Related