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
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)..
}
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
)
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With