Search code examples
javaspring-boot-testmapstruct

@SpringBootTest with MapStruct requires Impl


I have following test:

@SpringBootTest(classes = {SomeService.class, DtoMapperImpl.class})
class SomeServiceTest {

And following mapper:

@Mapper(componentModel = "spring")
public interface DtoMapper {
    EntityDto toDto(Entity entity);
}

I'm not changing packages (this means DtoMapperImpl is in the same package as DtoMapper)

As soon as I change Impl to interface my test fails:

@SpringBootTest(classes = {SomeService.class, DtoMapper.class})
class SomeServiceTest {

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'someService': Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'DtoMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Can you please advise best way solving this? I'm on MapStruct 1.3.1.Final


Solution

  • The problem actually has nothing to do with MapStruct, but rather to how SpringBootTest#classes is used.

    The classes in SpringBootTest is meant to provide your components that should be used to load in the test.

    From the JavaDoc:

    The component classes to use for loading an ApplicationContext. Can also be specified using @ContextConfiguration(classes=...). If no explicit classes are defined the test will look for nested @Configuration classes, before falling back to a @SpringBootConfiguration search. Returns: the component classes used to load the application context

    In your case you have 2 classes:

    • SomeService - which I assume is a class annotated with @Service and Spring will correctly load it
    • DtoMapper - this is the MapStruct mapper which is an interface and it isn't a component. The component which you want for your tests is DtoMapperImpl

    You have several options to fix this:

    Use the Impl class

    You can use the DtoMapperImpl (the Spring Component class) in your SpringBootTest#classes, your test will then load the correct component

    Use a custom configuration class that will component scan your mappers

    @TestConfiguration
    @ComponentScan("com.example.mapper")
    public class MappersConfig {
    
    }
    

    And then use this in your SpringBootTest#classes. E.g.

    @SpringBootTest(classes = {SomeService.class, MappersConfig.class})
    class SomeServiceTest {
       ...
    }