How to write Junit test for mapstruct abstract mapper injected via Spring

Viewed 50730

I'm using MapStruct, mapstruct-jdk8 version 1.1.0.Final and defining abstract class that I inject via Spring.

I'm looking at how to be able to test them via Junit Test ? I've basicaly a main mapper that will use 2 sub mappers

@Mapper(componentModel = "spring", uses = {SubMapper1.class, SubMapper2.class})
public abstract class MainMapper {

  @Mapping(target = "field1", qualifiedByName = {"MyMapper2Name", "toEntity"})
  public abstract MyEntity toEntity(MyDto pDto);

  public MyDto fromEntity(MyEntity pEntity) {
     // Specific code, hence why I use Abstract class instead of interface. 
  }
}

I've tried several things but can't get the mapper to be instancied correctly to test it.

@RunWith(SpringRunner.class)
public class MainMapperTest {

    private MainMapper service = Mappers.getMapper(MainMapper.class);

    @Test
    public void testToEntity() throws Exception {
.....

java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.mappers.MainMapper

I've also tried via @InjectMock but no dice either.

Cannot instantiate @InjectMocks field named 'service'. You haven't provided the instance at field declaration so I tried to construct the instance. However, I failed because: the type 'MainMapper is an abstract class.

And via Spring @Autowired

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.mappers.MainMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

I'm guessing this might have to do with annotation processor, and mapper not being generated when I launch test. I found this class as example.

However the class AnnotationProcessorTestRunner doesn't seems to be available before 1.2 which has no final release yet.

So my question is how do I write Junit tests to test my mapstruct abstract class mapper that I use via Spring injection in my code.

8 Answers

Addition to @TheBakker's answer: as a lighter alternative to @SpringBootTest you can use @ContextConfiguration, if you do not require the whole SpringBoot stack. His example would look like this:

@ExtendWith(SpringExtension.class) // JUnit 5
@ContextConfiguration(classes = {
            ConfigurationMapperImpl.class,
            SubMapper1Impl.class,
            SubMapper2Impl.class
        })
public class ConfigurationMapperTest {
...

With JUnit 4 use annotation RunWith instead of ExtendWith:

@RunWith(SpringRunner.class)       // JUnit 4
...

Using Mockito:

@Spy
private EntityMapper entityMapper = Mappers.getMapper(MyMapper.class);

And remeber to injects mocks in your class under test by, for example:

@InjectMocks
private MyClassUnderTest myClassUnderTest

Assumming that:

  • Your MainMapper mapper gets injected into @Component ConverterUsingMainMapper

You can use the following example:

@RunWith(SpringRunner.class)
@ContextConfiguration
public class ConsentConverterTest {

    @Autowired
    MainMapper MainMapper;

    @Autowired
    ConverterUsingMainMapper converter;

    @Configuration
    public static class Config {

        @Bean
        public ConverterUsingMainMapper converterUsingMainMapper() {
            return new ConverterUsingMainMapper();
        }

        @Bean
        public MainMapper mainMapper() {
            return Mappers.getMapper(MainMapper.class);
        }
    }


    @Test
    public void test1() {
        // ... your test.
    }

}

You can also manually create an application context with your mapper and its collaborators:

ApplicationContext context = new AnnotationConfigApplicationContext(MainMapperImpl.class, SubMapper1Impl.class, SubMapper2Impl.class);

or

ApplicationContext context = new AnnotationConfigApplicationContext("package.name.for.your.mappers");

Then get your Mapper from context

MainMapper mainMapper = context.getBean(MainMapper.class);

It works One hundred percent !

@SpringBootTest
 public class CustomMapperTest {

@Spy
private CustomMapper mapper = Mappers.getMapper(CustomMapper.class);

@Test
public void SCENARIO_CONVERT_ACTIVITY() {
    ActivityEntity dbObj = mapper.toDto(ActivityDTO);
    Assert.assertNotNull(dbObj);
    Assert.assertEquals("XXXX", dbobj.getId());
}

@Mapper
public abstract Class CustomMapper{
  @Mapping(source="activityDto.fathername",target="surname")
  public abstract ActivityEntity toDto(ActivityDTO activityDto);
}

In order to mapstruct you will use below

Mappers.getMappers(Your Mapper Class here.class)

E.g:-

OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class );

Use the INSTANCE Variable and access your mapper class methods here.

And then run with SpringRunner.class

@RunWith(SpringRunner.class)

If you find issue with implants not found or class not found on the Test class which is being tested. Then do maven clean test like below.

mvn clean test

Then you should be fine with your testing.

Reference:-

https://mapstruct.org/development/testing-mapstruct/

Related