0
votes

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

2
Because you are using @SpringBootTest wrong. That is for spring boot integration tests, you want to test your service then don't use @SpringBootTest for that. - M. Deinum
Sorry, that doesn't answer my question - tillias
As stated you are using the wrong tool for the job or you are using the tool wrong in this case. You either write a full integration test (and then remove the classes part) OR you write a simple unit test for your service and manually inject the mapper by obtaining it from MapStruct itself. - M. Deinum
Sorry, just wanted to point out that your comments are off topic in this question. If you want to discuss ideology, how to write integration tests, I will be happy to do so outside of this question. Mods, please cleanup - tillias
It isn't about ideologie it is about using the wrong tool for your test, which is what I'm trying to explain. - M. Deinum

2 Answers

0
votes

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 {
   ...
}
0
votes

Create following configuration (should point where mappers are):

@TestConfiguration
@ComponentScan("some.package.mapper")
public class MappersConfig {
}

And modify slice:

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