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

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.

Related

How to create some test spring beans before others

I have production bean A and in #TestConfiguration bean B. Non of those beans depends on each other. But in tests I want bean B to be instantiated before bean A. that's because B prepares local testing environment for A. How can i achieve it?
i can't just annotate A with #DependsOn because B is available only in some tests and i don't want to bind production code with tests.
But in tests I want bean B to be instantiated before bean A
...
and i don't want to bind production code with tests.
Something like this?
#Profile("test")
#Bean
public BeanB beanB() {
return new BeanB();
}
#Profile("test")
#Bean
public BeanA beanA(BeanB beanB) {
return new BeanA();
}
#Profile("!test")
#Bean
public BeanA beanA() {
return new BeanA();
}
There's no easy way to do what you're asking. Your best bet is to have an optional dependency in A. In production, the dependency will be missing and so will be ignored. In the tests, you will provide a bean which contains your test preparation implementation.
Yes, you are adding a little bit of additional complexity for the sake of tests which is not ideal but it's very limited.
Forgive the terrible names:
interface APreparer {
void prepareA();
}
#Component
public class A {
public A(final Optional<APreparer> preparer) {
preparer.ifPresent(APreparer::prepareA);
}
}
You can create a TestApplication (in src/test/java) that trivially extends your actual Spring Boot application class and have that depend on your test bean. Then tell SpringBootTest to load your TestApplication instead of the production application.
Example TestApplication:
#DependsOn("beanB")
#Configuration
#Import(YourApplication.class)
public class TestApplication {}
Example test class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = TestApplication.class)
public class YourApplicationTests {
...

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";
}
}
}

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 :-)

Spring boot test - No qualifying bean of type 'com.example.MyService' available

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().

Spring Boot: #TestConfiguration Not Overriding Bean During Integration Test

I have a Bean defined in a class decorated with #Configuration:
#Configuration
public class MyBeanConfig {
#Bean
public String configPath() {
return "../production/environment/path";
}
}
I have a class decorated with #TestConfiguration that should override this Bean:
#TestConfiguration
public class MyTestConfiguration {
#Bean
#Primary
public String configPath() {
return "/test/environment/path";
}
}
The configPath bean is used to set the path to an external file containing a registration code that must be read during startup. It is used in an #Component class:
#Component
public class MyParsingComponent {
private String CONFIG_PATH;
#Autowired
public void setCONFIG_PATH(String configPath) {
this.CONFIG_PATH = configPath;
}
}
While trying to debug this I set a breakpoint inside each method as well as the constructor of the test config class. The #TestConfiguration's constructor breakpoint is hit, so i know that my test configuration class instantiates, however the configPath method of that class is never hit. Instead, the configPath method of the normal #Configuration class is hit and the #Autowired String in MyParsingComponent is always ../production/environment/path rather than the expected /test/environment/path.
Not sure why this is happening. Any thoughts would be greatly appreciated.
As documented in the Detecting Test Configuration section of the Spring Boot reference manual, any beans configured in a top-level class annotated with #TestConfiguration will not be picked up via component scanning. So you have to explicitly register your #TestConfiguration class.
You can do that either via #Import(MyTestConfiguration.class) or #ContextConfiguration(classes = MyTestConfiguration.class) on your test class.
On the other hand, if your class annotated with #TestConfiguration were a static nested class within your test class, it would be registered automatically.
Make sure that the method name of your #Bean factory method does not match any existing bean name. I had issues with method names like config() or (in my case)
prometheusConfig() which collided with existing bean names. Spring skips those factory methods silently and simply does not call them / does not instantiate the beans.
If you want to override a bean definition in your test, use the bean name explicitly as string parameter in your #Bean("beanName") annotation.
Test configuration has to be explicitly imported in the test via #Import({MyTestConfiguration.class}).
The name of the #Bean methods in #Configuration and #TestConfiguration have to be different. At least it makes difference in Spring Boot v2.2.
Also make sure spring.main.allow-bean-definition-overriding=true otherwise the bean could not be overriden.
For me worked this code:
#TestConfiguration // 1. necessary
public class TestMessagesConfig {
#Bean
#Primary // 2. necessary
public MessageSource testMessageSource() { // 3. different method name than in production code e.g. add test prefix
}
}
I struggled with a related problem, whereby even though I was using an inner static class, my test bean was not being registered.
It turns out, You still need to add your inner static class to the #ContextConfiguration class array, otherwise the beans inside the #TestConfiguration doesn't get picked up.
public interface Foo {
String execute();
}
public class FooService {
private final Foo foo;
FooService(Foo foo) {
this.foo = foo;
}
public String execute() {
return foo.execute();
}
}
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {FooService.class, FooTest.FooTestConfig.class})
public class FooTest {
#Autowired
FooService fooService;
#Test
void test() {
Assertions.assertEquals("MY_TEST_BEAN", fooService.execute());
}
#TestConfiguration
static class FooTestConfig {
#Bean
public Foo getFooBean() {
return () -> "MY_TEST_BEAN";
}
}
}
I came across a similar issue recently and got it sorted out by annotating my testing bean with #Primary as well as #Bean. Not sure why it's required, which seems not documented in the Spring doc. The version of my SpringBoot is 2.0.3.
In my case it was an issue with #RunWith(SpringRunner.class), I'm not exactly sure why it wasn't working, I was following this - Testing in Spring Boot
But after replacing that with #ExtendWith(SpringExtension.class) the inner static #TestConfiguration class created the beans as expected.
Maybe a version mismatch - I'm using Spring Boot 2.7.2.
In my case replacing #Import(TestConfig.class) with #ContextConfiguration(classes=TestConfig.class) did the trick. For some reason, some of the beans from TestConfig but 1 wasn't until I replaced #Import with #ContextConfiguration.
This was also mentioned in some comments that were hidden because they had no upvotes.
I found it odd how several answers stated that the names of the #Beans have to be different from each other. How would that make one override the other?
There wasn't one specific answer that worked for me, but I've solved the issue by combining some of their advices.
Here's what worked for me.
Main configuration class:
#Configuration
public class SpringConfiguration {
#Bean
BeanInterface myBean() {
return new BeanImplementation();
}
#Bean
OtherClass otherBean() {
return new OtherClass();
}
}
Test configuration class:
#TestConfiguration
public class TestSpringConfiguration {
#Bean
#Primary
BeanInterface myBean() {
return new TestBeanImplementation();
}
}
Test class:
#SpringBootTest(classes = TestSpringConfiguration.class,
properties = "spring.main.allow-bean-definition-overriding=true")
public class Tests {
#Test
public void test() {
// do stuff
}
}
In this way, the "myBean" bean instance is the one defined in the TestSpringConfiguration class, while "otherBean" is the one defined in the SpringConfiguration class, since it's not overridden.
If I gave two different names to the "myBean" beans, the "real" one would still be initialized and, in my case, would give an error during tests, since it needs something that's only available at runtime in its proper environment.
Once I gave both the same name, Spring would throw an error saying that they were conflicting. Hence why I had to specify the property spring.main.allow-bean-definition-overriding=true in the #SpringBootTest annotation of the test class.
By the way, if you're NOT using Spring Boot, I guess these alternative annotations could work for you:
#ExtendWith(value = SpringExtension.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, // <- not sure about this one
classes = { SpringConfiguration.class, TestSpringConfiguration.class })
public class Tests {
#Test
public void test() {
// do stuff
}
}
Then, you would still have to set the property spring.main.allow-bean-definition-overriding=true in the test application.yml or application.properties file, or in some other way via code on startup.
Note: I'm not 100% sure that you would need the loader = AnnotationConfigContextLoader.class thing. Try without it, first. I needed it in a project of mine which had Spring without Boot, but I can't remember whether it's a standard thing to set or I needed it for some specific reason.

Categories

Resources