Spring Boot Integration test mock external dependency - java

I'm trying to create integration tests for my Spring Boot app. The idea is to launch an embedded postgres db and run http calls with TestRestTemplate to my controllers.
The problem is my project has a dependency we use for redis queues.
<dependency>
<groupId>com.github.sonus21</groupId>
<artifactId>rqueue-spring-boot-starter</artifactId>
<version>2.9.0-RELEASE</version>
</dependency>
I've tried to mock out the dependencies and most of them work, but with this one it complains I guess because it is a #Configuration not a #Component:
Dependency config class:
#Configuration
#AutoConfigureAfter({RedisAutoConfiguration.class})
#ComponentScan({"com.github.sonus21.rqueue.web", "com.github.sonus21.rqueue.dao"})
public class RqueueListenerAutoConfig extends RqueueListenerBaseConfig {
public RqueueListenerAutoConfig() {
}
...
}
My test config class
#TestConfiguration
public class TestRestTemplateConfig {
#Bean
#Primary
#Order(Ordered.HIGHEST_PRECEDENCE)
public RqueueListenerAutoConfig rqueueListenerAutoConfig() {
return Mockito.mock(RqueueListenerAutoConfig.class);
}
....
}
I've tried with #AutoConfigureOrder(1) at my config class but the original RqueueListenerAutoConfig launches before anything and my mocked beans haven't been declared yet.
To be honest mocking every service on that dependency is a pain, but I haven't figured out a way to mock the whole dependency with a single configuration. I tried not loading the dependency when I'm on the test profile but since it runs spring context my code needs it.
My test class has the following config:
#SpringBootTest
#Import(TestRestTemplateConfig.class)
#ActiveProfiles("test")
public class TestClass {
...
}
Any clues?
Thanks.

Try
#EnableAutoConfiguration(exclude=RqueueListenerAutoConfig.class)

Related

Spring boot test with multiple configuration

In my Spring boot 2.1 project I have different #Configurations for different test (ConfigurationA and ConfigurationB), that reside in different packages. Both configurations define the same set of beans but in a different manner (mocked vs. the real thing)
As I am aware of the Bean overriding mechanism introduced in Spring Boot 2.1, I have set the property: spring.main.allow-bean-definition-overriding=true.
However I do have a test with the following the setup of the following configuration and test class. First there is a #Configuration in the productive part (I'm using Maven):
package com.stackoverflow;
#Configuration
public class ProdConfiguration{
...
}
Then in the test branch there is a general Test #Configuration on the same package level:
package com.stackoverflow
#Configuration
public class TestConfiguration {
#Bean
public GameMap gameMap() {
return Mockito.mock(GameMap.class);
}
}
And in a subpackage I have another #Configuration:
package com.stackoverflow.impl;
#Configuration
public class RealMapTestConfiguration {
#Bean
public GameMap gameMap() {
return new GameMap("testMap.json");
}
}
And then of course there is the test that is troubling me:
package com.stackoverflow.impl;
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration(classes={RealMapTestConfiguration.class, ProdConfiguration.class})
#ActiveProfiles("bug") // spring.main.allow-bean-definition-overriding=true
public class MapImageServiceIT {
#Autowired
private GameMap map;
}
It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration. Aparrently in my test I have the configuration from ProdConfiguration and TestConfiguration, when I wanted ProdConfiguration and RealMapTestConfiguration. As the beans defined in the ProdConfiguration and *TestConfiguration are different the combination works, but TestConfiguration and RealMapTestConfiguration define the same been. It seems like the TestConfiguration is picked up by component scanning as it is in the same package as ProdConfiguration.
I was under the impression that when overriding beans the bean definition that is closer to the test class would be preferred. However this seems not to be the case.
So here are my questions:
When overriding beans, what is the order? Which bean overrides which one?
How to go about to get the correct instance in my test (using a different bean name is not an option, as in reality the injected bean is not directly used in the test but in a service the test uses and there is no qualifier on it.)
I've not used the spring.main.allow-bean-definition-overriding=true property, but specifying specific config in a test class has worked fine for me as a way of switching between objects in different tests.
You say...
It turns out that the injected GameMap into my test is a mock instance from TestConfiguration instead of the real thing from RealMapTestConfiguration.
But RealMapTestConfiguration does return a mock
package com.stackoverflow.impl;
#Configuration
public class RealMapTestConfiguration {
#Bean
public GameMap gameMap() {
return Mockito.mock(GameMap.class);
}
}
I think the problem here is that including ContextConfiguration nullifies (part of) the effect of #SpringBootTest. #SpringBootTest has the effect of looking for #SpringBootConfiguration in your application (starting from the same package, I believe). However, if ContextConfiguration is applied, then configurations are loaded from there.
Another way of saying that: because you have ContextConfiguration in your test, scanning for #Configuration classes is disabled, and TestConfiguration is not loaded.
I don't think I have a full picture of your configuration setup so can't really recommend a best practice here, but a quick way to fix this is to add TestConfiguration to your ContextConfiguration in your test. Make sure you add it last, so that it overrides the bean definitions in the other two configurations.
The other thing that might work is removing #ContextConfiguration entirely and letting the SpringBootApplication scanning do its thing - that's where what you said about the bean definition that is closest may apply.
In that case just don't use #Configuration on configuration class and import it to the test manually using #Import, example:
#SpringBootTest
#Import(MyTest.MyTestConfig.class)
public class MyTest {
#Autowired
private String string;
#Test
public void myTest() {
System.out.println(string);
}
static class MyTestConfig {
#Bean
public String string() {
return "String";
}
}
}

Why are my dependency Spring beans instantiated twice ? Once from the jar dependency and once from the classpath

I have the most puzzling Spring error message I ever had, well, I had a few over the years, but this one is one to remember.
The error message is:
Field orderService in com.thalasoft.butik.rest.config.FixtureService required a single bean, but 2 were found:
- com.thalasoft.butik.data.service.OrderServiceImpl: defined in URL [jar:file:/home/stephane/.m2/repository/com/thalasoft/butik-data/0.0.1-SNAPSHOT/butik-data-0.0.1-SNAPSHOT.jar!/com/thalasoft/butik/data/service/OrderServiceImpl.class]
- OrderService: defined by method 'OrderService' in class path resource [com/thalasoft/butik/data/config/JpaService.class]
The butik application is composed of 2 Spring projects, one being the butik-data project and the other one being the butik-rest project.
The error occurs when running the integration tests in the butik-rest project
mvn clean install -Denv="test" -Ddb="h2"
The very same error occurs when running the application and not running the integration tests:
mvn clean spring-boot:run
The dependency is only present once in the pom.xml file:
<dependency>
<groupId>com.thalasoft</groupId>
<artifactId>butik-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
My butik-rest project configuration looks like:
#EnvProd
#SpringBootApplication
#Slf4j
public class Application implements CommandLineRunner {
#Component
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.service", "com.thalasoft.butik.data" })
public class ApplicationConfiguration {
}
#Component
#EnableWebMvc
#EnableSpringDataWebSupport
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.exception",
"com.thalasoft.butik.rest.controller", "com.thalasoft.butik.rest.assembler" })
public class WebConfiguration implements WebMvcConfigurer {
The integration test configuration:
#RunWith(SpringRunner.class)
#Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = {
"classpath:mysql/clean-up-before-each-test.sql" })
public abstract class BaseTest {
#Configuration
#EnableAutoConfiguration
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.config",
"com.thalasoft.butik.rest.service", "com.thalasoft.butik.data" })
public class TestConfiguration {
}
#EnableWebSecurity
#ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages = { "com.thalasoft.butik.rest.filter" })
public class NoSecurityConfiguration extends WebSecurityConfigurerAdapter {
The service beans are instantiated explicitly in the dependency project:
#Configuration
public class JpaService {
#Bean
public ProductServiceImpl ProductService() {
return new ProductServiceImpl();
}
#Bean
public OrderServiceImpl OrderService() {
return new OrderServiceImpl();
}
}
Could it be that Spring gets one bean from the explicit instantiation above in the butik-data project, and another one from the "com.thalasoft.butik.data" scanning in the dependant butik-rest project ?
UPDATE: Even when changing the 2 instances of "com.thalasoft.butik.data" (there is one to run the application and another one to run the integration tests) into "com.thalasoft.butik.data.config" I still get the same error.
UPDATE: I see I had 2 mistakes compounding making the whole issue a bit tricky. I had to remove the "com.thalasoft.butik.data.config" instance from the integration tests also. And now the issue is gone.
It looks like you've scanned both of those locations. You need to investigate places which are currently scanned and which should be scanned.
If you think that current way of scanning (which include two beans which are suitable for autowiring 'orderService' field), you can mark one of those beans by annotation #Primary ( docs: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Primary.html ).
Bean marked by this annotation will be prefered over another ones, which should solve your problem.
Good luck :)

Spring MVC - Override a bean for one Integration test

I have a project with spring MVC v5.0.8, Java 8
I've made some integration test from controller to database, and now, I want to add one which will test what happens if the first part of a transactional behavior failed. I'll ensure that the transaction is effectively roll-backed.
So, I've to override a DAO to make it fail. After some research, came up a simple idea : override spring config for that test : Overriding an Autowired Bean in Unit Tests
My test work fine now, but the problem is that this configuration is shared with other test from other classes, even if it is in one class. How can I make it specific to that test ?
(If b creation fail, a must be roll-backed)
Failing test :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#EnableWebMvc
#Sql({"/sqlfiles/clean-data.sql"})
public class AControllerFailDaoIt {
#Configuration
static class ConfigFailDao extends ApplicationConfiguration{
#Bean
#Primary
public BDao getBDao() {
return new BDao() {
//Overriding method to make it fail
};
}
}
#Autowired
AController aController;
#Autowired
ADao aDao;
#Test
public void should_not_create_a_if_b_failed(){
//creation of a
//assert nor a nor b are created
}
}
Nominal test case :
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#EnableWebMvc
#ContextConfiguration(classes = {ApplicationConfiguration.class, CustomWebAppConfiguration.class})
#Sql({"/sqlfiles/clean-data.sql"}) //"/sqlfiles/account-test.sql"
public class AControllerIT {
#Autowired
AController aController;
#Autowired
ADao aDao;
#Autowired
BDao bDao;
#Test
public void should_create_a_and_corresponding_b(){
//create a
//assert that both a and b are created
}
}
ApplicationConfiguration (which is test-specific)
#Configuration
#ComponentScan(basePackages = "my.base.package")
class ApplicationConfiguration implements WebMvcConfigurer {
}
Note : my Integration test classes are within the base package, does it matters ?
I found an option : put the fail test config in an external class, and call it only on my fail test, but it still doesn't work.
At the moment, I ran out of ideas, so I'll welcome your help !
I suggest you to use #Qualifier annotation.
Instead of putting #Primary above getBDao() method in your configuration put #Qualifier with some name i.e.:
#Bean
#Qualifier("BDaoTest")
public BDao getBDao() {
return new BDao() {
//Overriding method to make it fail
};
}
After that put #Primary on your default BDao implementation (in your default config).
Then when you autowire this bean in your test you need to put this qualifier:
#Autowired
#Qualifier("BDaoTest")
BDao bDao;
I got it working. In a bad way, so if you have a better option, I'm in. But I didn't found a way to make my test fail by data... Which would be the better one.
So, I've found that the #Configuration annotation make a class scanned. Spring doc.
I just made my fail configuration in an outer class, and remove the #Configuration annotation. So it won't be scanned by other test configuration. Then in my fail test class, I referenced it in the #ContextConfiguration annotation, so it is used. This way it work fine. I now have a problem with #Transactional, but that's not this thread.
Thanks to responders, you made me look in the right direction :-)

Why is WebMvcConfigurer being loaded?

I'm new to Spring and testing a Spring Data project with Postgresql/JPA and MongoDB components. My Test class has the following annotations:
#SpringBootApplication
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {SpringMongoConfig.class, PgRepository.class, MongodbRepository.class})
public class PerfTest {
#Autowired
private PgRepository pgRepo;
#Autowired
private MongodbRepository mongoRep;
For some reason the spring-context module is trying to load WebMvcConfigurer while resolving bean classes, giving me a NoClassDefFoundError.
Is the Spring Boot Autoconfigure trying to initiate a full web controller suite? If so, why? I'm really just interested in Spring Data. Should I avoid Spring Boot entirely?
You shouldn't annotate a test class as a #SpringBootApplication. This annotation is used to define a class in your main code base which contains a main() method to spin up your Spring boot container with auto-configuration.
If it's Spring data you are interested in testing then your tests will need to point to a #Configuration class which is annotated with #EnableJpaRepositories this will allow your repository interfaces to be autowired.
I don't know what is in your SpringMongoConfig class but if this contains that annotation then all you need to do is:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {SpringMongoConfig.class})
public class PerfTest {
If not then you need to set up this configuration and include it in the #ContextConfiguration of the test.
Another option which is a bit more heavyweight but will guarantee everything is available for your test (provided your Spring boot application is set up correctly) is to mark your test with #SpringBootTest this will automatically load the whole Spring boot context for the application. If it has trouble finding your main class you can point it in the right direction by providing the class in the annotation:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyMainClass.class)
public class PerfTest {
You need to add #Component to your repository interface. Spring loads the component class first and then #AutoWiring & Initialization of variables will happen.

How can I use ActiveProfiles with Spring Boot tests with WebMvcTest?

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?

Categories

Resources