#SpringBootTest with MapStruct requires Impl - java

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

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

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 {

I would suggest a little improvement for accepted answer so that you don't have to write package name as hard-coded string, but use ComponentScan basePackageClasses instead:
#TestConfiguration
// #ComponentScan("some.package.mapper")
#ComponentScan(basePackageClasses = DtoMapper.class)
public class MappersConfig {
}
But there is an obvious drawback in this (as well as accepted answer) approach: the whole package will be scanned which may contain undesired classes.

Related

Configuring Spring's #DataJpaTest that overrides default application context beans

I'm struggling with configuration for my #DataJpaTest. I'd like to take advantage of auto-configured spring context provided by #DataJpaTest, but I'd like to override some of it's beans.
This is my main class:
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(BookInputPort bookInputPort) {
return args -> {
bookInputPort.addNewBook(new BookDto("ABC", "DEF"));
bookInputPort.addNewBook(new BookDto("GHI", "JKL"));
bookInputPort.addNewBook(new BookDto("MNO", "PRS"));
};
}
As you can clearly see, I provide my implementation for CommandLineRunner that depends on some service.
I also have a test:
#DataJpaTest
public class BookRepositoryTest {
public static final String TITLE = "For whom the bell tolls";
public static final String AUTHOR = "Hemingway";
#Autowired
private BookRepository bookRepository;
#Test
public void testRepository() {
Book save = bookRepository.save(new Book(TITLE, AUTHOR));
assertEquals(TITLE, save.getTitle());
assertEquals(AUTHOR, save.getAuthor());
}
}
When I run the test I get the following error:
No qualifying bean of type 'com.example.demo.domain.book.ports.BookInputPort' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
That makes perfect sense! Auto-configured test provides implementation only for a 'slice' of the context. Apparently implementation of BookInputPort is missing. I do not need this commandLineRunner in a test context. I get create a commandLineRunner that does not depend on any service.
I can try to solve the issue by adding to my test class nested class :
#TestConfiguration
static class BookRepositoryTestConfiguration {
#Bean
CommandLineRunner commandLineRunner() {
return args -> {
};
}
}
That solves the problem. Kind of. If I had more tests like this I would have to copy-paste this nested class to each test class. It's not an optimal solution.
I tried to externalize this to a configuration that could be imported by #Import
This is the config class:
#Configuration
public class MyTestConfiguration {
#Bean
public CommandLineRunner commandLineRunner() {
return args -> {
};
}
}
But then the application fails with a message:
Invalid bean definition with name 'commandLineRunner' defined in com.example.demo.DemoApplication: Cannot register bean definition
I checked that error and other folks on SO suggested in this case:
#DataJpaTest(properties = "spring.main.allow-bean-definition-overriding=true")
I did that and I got:
No qualifying bean of type 'com.example.demo.domain.book.ports.BookInputPort' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
That is exactly the same problem I started with.
I took all these steps and found myself in the place where I was at the very beginning.
Do you have any idea how to solve this issue? I do not have a vague idea or clue.
#DataJpaTest generally starts scanning from current package of the test class and scans upwards till it finds the class annotated with #SpringBootConfiguration. So creating a SpringBootConfiguration class at the repository root package will create only beans defined within that package. On top of that we can add any customized test bean required for our test class in that configuration class.
#SpringBootConfiguration
#EnableAutoConfiguration
public class TestRepositoryConfig {
#Bean
public CommandLineRunner commandLineRunner() {
return args -> {
};
}
#Bean
public BookInputPort bootInputPort(){
return new BookInputPort();
}
}
In Spring Boot test class you can do it like this
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class YourClassTest {
#Autowired
private YourService yourService;
It does load all the services and I am using it perfectly fine
How to load specific entities in one environment and not the other
You can use #Profile annotation in your entities.
Put #Profile({"prod"}) on entities/services you don't want to load in test run, and
Put #Profile({"prod", "test"}) on entities/services you want to load in both test and prod environments
Then run the test with test profile. It will not load unnecessary entities.
You can put #Profile annotation on other services too.
The annotation #DataJpaTest only loads the JPA part of a Spring Boot application, the application context might not loaded because you have #DataJpaTest annotation. Try to replace the #DataJpaTest with #SpringBootTest.
As per Spring documentation:
By default, tests annotated with #DataJpaTest will use an embedded
in-memory database (replacing any explicit or usually auto-configured
DataSource). The #AutoConfigureTestDatabase annotation can be used to
override these settings. If you are looking to load your full
application configuration, but use an embedded database, you should
consider #SpringBootTest combined with #AutoConfigureTestDatabase
rather than this annotation.

ComponentScan code smell? When running unit tests, I get Error creating bean, no qualifying bean of type, even though bean exists in Spring beans list

i have a config class in a config package that looks like this:
package com.x.y.appName.config
#ComponentScan(basePackageClasses = { Application.class })
#Configuration
public class AppConfig {
my project is failing on build within SomeClass that uses the AppConfig bean, saying:
Error creating bean with name 'someClass': Unsatisfied dependency
expressed through field 'appConfig'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type
But when I stdout print the list of beans Spring is aware of, it lists appConfig there
SomeClass is also in the config package, and looks like this:
package com.x.y.appName.config;
#Configuration
public class SomeClass implements WebMvcConfigurer {
#Autowired
AppConfig appConfig;
but if i add this to SomeClass, it builds fine and all tests pass:
#ComponentScan("com.x.y.appName.config")
in the past ive never needed to ComponentScan the same package that another bean is also declared in
again to clarify, i can bootRun the app just fine, but this spring error is throwing during build or test. do i need to add something to the unit tests? I dont have unit tests for either of the above classes as they would be too frivolous. So what could be going on? Do I need to annotate other unit tests somewhere?

Testing a spring boot controller which has dependencies? (jUnit)

I have this test class :
#RunWith(SpringRunner.class)
#WebMvcTest(ClassToBeTested.class)
public class ClassToBeTestedTests {
#Autowired
private MockMvc mockMvc;
#Test
public void simpleTestMethodToGetClassWorking(){
Assert.assertTrue(true);
}
}
but in the class I want to test, I have this line :
#Autowired
AnnoyingServiceWhichIsADependency annoyingDependency;
So when I try to run the test class - I get this error :
java.lang.IllegalStateException: Failed to load ApplicationContext
and the cause by line seems to throw this up :
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ClassToBeTested': Unsatisfied dependency expressed through field 'AnnoyingServiceWhichIsADependency'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type '<package-path>.AnnoyingServiceWhichIsADependency' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I will add that the actual class does work, and does what it is meant to do, but I am having trouble making it work in the unit test world.
All help appreciated.
The reason a bean is not created for the dependency class is that you're using #WebMvcTest and not #SpringBootTest: only controllers and the MVC infrastructure classes are scanned. From the docs:
Can be used when a test focuses only on Spring MVC components.
Since it's an MVC test, you can mock the service dependency.
Example: https://reflectoring.io/spring-boot-web-controller-test/
#WebMvcTest is only going to scan the web layer- the MVC infrastructure and #Controller classes. That's it. So if your controller has some dependency to other beans from, e.g. form your service layer, they won't be found to be injected.
If you want a more comprehensive integration test, use #SpringBootTest instead of #WebMvcTest
If you want something closer to a unit test, mock your dependency.
Also note that Field injection (#Autowired directly on the field) is not recommended exactly for these reasons. I recommend you change to constructor injeciton ( add a constructor for Classtobetested and place the #Autowired annotation on it. ) Then for a unit test you can pass in a mock. Constructor injection leads to a more testable and configurable design.
Your test application context is trying to load your ClassToBeTested but is unable to find one of its dependencies and complains about it via that error. Basically you need to have a #Bean of that type in your test context. An option will be to create a TestConfig class which provides a Mock/Spy of that dependency via #Bean annotation. In your test you will have to load inside the context via the #ContextConfiguration annotation this test config you just created.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#spring-testing-annotation-contextconfiguration
Just mock that dependency. Assuming that AnnoyingServiceWhichIsADependency is an interface:
#RunWith(SpringRunner.class)
#WebMvcTest(ClassToBeTested.class)
public class ClassToBeTestedTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private AnnoyingServiceWhichIsADependency annoyingDependency;
#Test
public void simpleTestMethodToGetClassWorking(){
Assert.assertTrue(true);
}
}
Use Mockito when and thenReturn methods to instruct the mock.

How to create a mock spring bean for a class having autowired dependencies

Suppose I have a class called MainClass.
public class MainClass {
#Autowired
AutoWiredClass autoWiredClass;
}
I am trying to create a mock bean of MainClass using Mockito.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class AutowiringTest {
#Configuration
static class AutowiringTestConfiguration{
#Bean
public MainClass mainClass() {
return Mockito.mock(MainClass.class);
}
}
#Autowired
MainClass mainClass;
#Test
public void testBeanCreation(){
assertNotNull(mainClass);
}
}
I am getting this error while running the test case.
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: autowiring.AutoWiredClass autowiring.MainClass.autoWiredClass; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [autowiring.AutoWiredClass] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I know I can achieve this using #Mock and #InjectMocks. But that's not the solution I want.
My requirement is to create a mock bean of MainClass without creating an actual bean of AutowiredClass. Please help me how to achieve this.
As Florian has already commented, you should try to create tests that do not need Spring at all, and you will not have those problems.
But, if there is no workaround possible, you can use a bit of magic with the AutoMockRegistryPostProcessor.
You just need to add the AutoMockRegistryPostProcessor to the #ContextConfiguration, and it will create mocks for your missing dependencies:
#ContextConfiguration(classes = { AutowiringTest.class, AutoMockRegistryPostProcessor.class })
public class AutowiringTest {
// no complains anymore, a mockito mock will be created for AutoWiredClass
The AutoMockRegistryPostProcessor class is not in maven, you will need to copy it in your project.
The docu is here.

How to add inner classes to Spring application context for Unit Testing?

I have a bean whose business logic loads beans of a certain type from the ApplicationContext in order to process them.
For my jUnit tests, I would like to create some dummy beans within my unit test class and see if my bean under test properly processes them. However, I am not sure what the best way to accomplish this is.
If I just declare my inner class within my test class, Spring will not have it as part of its application context. I realize that I could inject my application context within my jUnit class, and then use appContext.registerPrototype() to add it, however, I thought there might be a cleaner way using annotations.
I've tried to annotate the internal class with #Component, but not surprisingly, it did not work.
public class PatchEngineTest extends TestBase {
#Component
protected class Patch1 extends PatchBaseImpl implements Patch{
public void applyPatch() throws Exception {
// does nothing
}
}
#Autowired PatchRepository patchRepository;
#Autowired Patch1 patch1;
#Test
public void test() {
fail("Not yet implemented");
}
}
Not surprisingly, I get the following error message:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.ia.patch.PatchEngineTest$Patch1] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Is there any way of doing this?
You need to make your inner class static; Spring can't instantiate a non-static inner class as a bean. If there really is a valid reason why it needs to be non-static, you could create it manually in an #Bean method.
I also met this problem, and below is my solution :
#RunWith(SpringRunner.class)
#SpringBootTest
#ComponentScan
#ImportAutoConfiguration
public class FooTest {
#Component
public static class Bar {
}
}
I used spring boot 2.1.4.RELEASE

Categories

Resources