I have a SpringBootTest
#ExtendWith(SpringExtension.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {PDFService.class},
properties = {
"spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=false",
})
#EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
class PDFServiceTest {
On start I got this exception
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'org.springframework.amqp.rabbit.core.RabbitTemplate' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
How can I disable RabbitMQ in my Tests? Or mock it.
It looks like RabbitMQ is disabled and that's actually a reason of the exception you get. Your code contains a bean that gets loaded (and autowired) in the test setup, that looks like this:
#Component
public class MyMessageManager {
#Autowired
private RabbitTemplate rabbitTemplate;
public void doSomething() {
....
rabbitTemplate.<...> // use the template
}
}
When spring test creates the instance of this bean it should be able to inject something that looks like a rabbit template, but since you've disabled it - there is no bean to be injected, hence the error.
How to solve it?
Well, there are many different approaches:
Use #MockBean in the test
#ExtendWith(SpringExtension.class)
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {PDFService.class},
properties = {
"spring.cloud.config.enabled=false",
"spring.cloud.discovery.enabled=false",
})
#EnableAutoConfiguration(exclude = {RabbitAutoConfiguration.class})
class PDFServiceTest {
...
#MockBean RabbitTemplate template;
Now the mock of rabbit template will be injected into all the beans that need it.
Refactor the code in a way that the class that uses RabbitTemplate won't be loaded. Usually it works well if you don't really want to test the functionality of that class.
Don't disable Rabbit MQ, instead, try using Test Containers and start rabbit mq before the test, create an infra that will define everything you need to run the code and inject a real rabbit template that works with the test container of rabbit. This is good if you actually want to test the message sending/receiving mechanism
Related
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.
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.
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.
There are a number of similar questions on stackoverflow, but I found none to be my case.
In my integration test with Spring boot 2.0.2.RELEASE, I created a separate #Configuration class for the test where I did define the bean com.example.MyService. This bean happens to be used by some other bean in com.example.OtherBean.
Here is the code:
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MyIntegrationTestConfig.class},
webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class MyService1Test extends MyAbstractServiceIntegrationTest {
#Test
public void someTest() {}
}
A common abstract for setup and teardown:
public class MyAbstractServiceIntegrationTest{
#Before
public void setUp(){}
#After
public void tearDown()
}
MyIntegrationTestConfig in src/test, to be used instead of the configs in the src/main:
#Configuration
#ComponentScan({"com.example"})
public class MyIntegrationTestConfig {
#Bean
public MyService myService() {
return null;
}
}
MyService bean can be null for testing purposes.
When I run the test, I keep getting the following error:
No qualifying bean of type 'com.example.MyService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
I even tried adding this inner class to MyServic1Test. Still didn't help:
#TestConfiguration
static class MyServic1TestContextConfiguration {
#Bean(name = "MyService")
public MyService myService() {
return null;
}
}
Any idea what I did wrong here? Or did I miss something?
My suspicion is that Spring tries to create/autowire beans in the src/main folder first before it even creates the MyService bean defined in src/test folder. Could that be the case? Or is there a different context (like test context) where bean MyService lives in, while the other beans live in an other context and could not locate MyService.
A side question: for integration test, it is OK to use webEnvironment = SpringBootTest.WebEnvironment.MOCK, correct?
The problem is how you are initializing the bean. The value null is the one that causes the issue. It is as if you weren't infact declaring any instance of that object. To make it work declare a valid instance of the service new MyService().
I have a unit test that is testing a RestController using #WebMvcTest. The the controller class autowires a service class that I would like to mock. I found that I can use #Profile and #Configuration to create a config class for specifying primary beans to use when a profile is active. I tried adding an active profile to my unit test class, but it says it failed to load the ApplicationContext. I'm not sure how I can do that while using #WebMvcTest.
#ActiveProfiles("mockServices")
#RunWith(SpringRunner.class)
#WebMvcTest(VoteController.class)
public class VoteControllerTest {
...
It seems I may be approaching this wrong. Any help is appreciated.
Edit:
Here is my configuration class:
#Profile("mockService")
#Configuration
public class NotificationServiceTestConfiguration {
#Bean
#Primary
public VotingService getVotingService() {
return Mockito.mock(VotingService.class);
}
}
The error I'm actually getting is:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'VotingService'
I was able to solve it by using #MockBean for VotingService in my Unit Test class. However, I want to use the Profile configuration I have in NotificationServiceTestConfiguration without having to call out mock beans.
Any thoughts?