How recreate only selected spring context in spring tests? - java

The typical exmaple is:
#ContextConfiguration(locations = {
"classpath:someITTest-mock.xml",
"classpath:someITTest-context.xml",
"classpath:commons.xml"})
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
public class SomeITTest {
//...
}
I am finding the way to recreate mocks during tests. The obvious way to use DirtiesContext
But it results to recreate all contexts, when only mock context should be recreated.
Here is several notes:
Context with mock seems to be loaded first, because other depended
context do not have such beans.
When mocks are created and injected
into depended context, it is probably impossible to cache/reuse
depended context.
So is there a way to recreate spring context where mocks are created, but leave other context cached?

Update: If you are using Spring Boot 1.4, you can use its first-class support for mocking beans via #MockBean.
So is there a way to recreate spring context where mocks are created, but leave other context cached?
No, that is not possible.
In your example, there is in fact only one single ApplicationContext which is loaded from all three XML configuration files. If you create a hierarchy using #ContextHierarchy, there would be multiple contexts; however, it is impossible to reload only a parent context (see other comments I've posted in this thread).
However, one common approach that people take in such scenarios is to explicitly reset the mocks in question.
There are basically two ways to achieve this.
If the mocks are injected into the integration test (e.g., via #Autowired) you can simply reset the mocks in an after method (e.g., in an #After method in JUnit 4).
Otherwise, you can implement and register a custom TestExecutionListener that retrieves the mocked beans from the ApplicationContext and resets them (e.g., in the afterTestMethod() method).
Regards,
Sam (author of the Spring TestContext Framework)

You may use #ContextHierarchy and #DirtiesContext:
#ContextHierarchy({
#ContextConfiguration(name="parent",
locations = {"classpath:someITTest-context.xml", "classpath:commons.xml"}),
#ContextConfiguration(name="child",
locations = "classpath:someITTest-mock.xml")
})
#DirtiesContext(hierarchyMode = CURRENT_LEVEL)
With this, only the child context should be reloaded, see Testing.

Related

Integration testing without Spring context

Should I always start Spring context during integration tests? (I mean using #SpringBootTest annotation)
Currently I'm writing integration test that involves a few classes and in order to make it faster I create object graph by hand (i.e. I don't start Spring IoC container). So currently my integration test (written in Spock) looks like this:
class UserConverterIT extends Specification {
UserConverter converter = new UserConverter(new UserDtoFactory(new UserGroupPolicy()))
def 'should ...'() {
when:
converter.convert(...)
then:
...
}
}
Alternatively I could add #SpringBootTest annotation, put #Autowire above UserConverter field and all dependecies would be injected automatically. However, the first approach is much faster. Is there something wrong in this approach?
As you said the #Autowired annotation would inject all dependencies and load the whole context automatically. Your approach is also working but its really fragile!
How can you guarantee that in your tests you never need some beans which you didn't manually new them?
Also there is another important thing. When you let the spring to inject the dependencies, if there is a problem on declaring the beans, problem would show on test phase, but in your approach they won't identify.
Also you might sometimes #Autowired an interface which told spring to get the implementation on runtime. For example you have a parent module which has an interface that implements in the child module. When you want to write a test case in parent you don't have access to child implementation to make new of that.

SpringBootTest - how to assert context not loads and change properties at test level?

I rely on #SpringBootTest heavily when testing application configuration. App properties can be complex, having default values and non-trivial validations. For example:
prop:
ports: 1205,2303,4039
fqdn: ${server.host}:${ports[0]}/${path}
#Configuration
SomeConfigClass{
#Value{"${ports}:{#collections.emptyList}"}
List<Integer> ports;
...
}
When testing such apps, I bring up a full or partial application context without mocks, as there is complexity in the context and validation itself - mocks don't capture this. Unfortunately, there are two limitations I keep finding with this pattern:
How can we test that bad configurations fail to load?
Imagine testing that the port in invalid because it is not on the restricted range of 500 - 1500.
#SpringBootTest(
classes = {SomeConfigClass.class},
properties = "port=9000"
)
public class BadConfigTest{
#Test(expected = ApplicationContextFailedException.class)
public void WHEN_port_9000_THEN_appcontext_fails_to_load() {}
}
Since the test framework loads after the application context, there appears to be no way to test that an app context fails to load. For now I actually write the tests, manually confirm they fail, and then annotation with #Ignored so they are not lost.
How to change properties at the test method, rather than class, level?
#SpringBootTest is a class annotation, meaning application properties are bound at the test-class level. This results in needing a test class for many sets of properties and bloats the test suite. For example, I'll end up with test classes like:
ConfigPropertiesAllValidTest
ConfigPropertiesNoneSetTest
ConfigPropertiesSomeValidSomeNotTest
Where each of these only has one or two test cases. Preferably, there'd be a single ConfigPropertiesTest class with different props for each test. Is this possible?
Again - I want to avoid mocks as they don't capture the non-trivial context autoconfiguration performed by Spring at runtime.
We ended up using the ApplicationContextRunner described in this document:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html#boot-features-test-autoconfig
You can use the #ActiveProfiles annotation along with the #SpringBootTest annotation to load properties for different profiles. This is class level, so will only help for case 1 of your question.
#SpringBootTest(classes = {SomeConfigClass.class})
#ActiveProfiles("badconfigtest")
public class BadConfigTest{
...
}
Then have an application-badconfigtest.properties with your bad config.
I don't think you'll find a way of changing properties between test methods in the same class. You can use #DirtiesContext to reload the application context, but I've not seen a way to use different property files. I guess you could inject the values into the config classes that have already loaded the properties.

Spring boot test: context loaded for every test?

In my project we have a super class for all our tests. This is the signature of that class
#RunWith(SpringRunner.class)
#SpringBootTest(value = {"management.port=0"}, classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#ActiveProfiles({"localhost", "test"})
#ContextConfiguration(classes = {Application.class, SomeConfiguration.class})
#Ignore
public abstract class AIntegrationTest {
Where Application.class is our main class, and SomeConfiguration.class it just for some #Bean and other stuff, nothing fancy.
I use gradle, and for running my tests I do:
./gradlew :my-project:test
My problems are:
I'm not sure if for each test the context is being initialized. But I can assure the context gets initialized multiple times. I know this by looking at the logs.
Since multiple contexts are initialized, it seems that contexts overlap with each other. I know this because one of the symptoms is this exception:
Caused by: org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [org.springframework.cloud.context.environment.EnvironmentManager#36408d9e] with key 'environmentManager'; nested exception is javax.management.InstanceAlreadyExistsException: RedeemAway:name=environmentManager,type=EnvironmentManager
Even if I don't care about the multiple contexts being loaded, is my impression that when a test finishes, the next test gets a new context BEFORE the previous one is terminated. I said this because of the overlapping of the exception from above.
Since all the tests share the same JVM, when some beans get registered twice, that exception rises up. From this link:
Context caching
It is said that:
An ApplicationContext can be uniquely identified by the combination of
configuration parameters that is used to load it. Consequently, the
unique combination of configuration parameters is used to generate a
key under which the context is cached. The TestContext framework uses
the following configuration parameters to build the context cache key
I understand that, but, I'm wondering how can I achieve that? My goal is to run all my tests over the same JVM and reuse the context with every test.
EDIT on Thu Feb 22
Things I tried:
spring.jmx.enabled: false
spring.jmx.default-domain: some-value
Really disabling JMX shouldn't help since the excpetion is around the EnvironmentManager, which is from Spring Cloud.
I found the answer to my problem. Here is well explained:
https://github.com/spring-projects/spring-boot/issues/7174
Basically, if you run a bunch of tests, as soon as one of them gets started, if it uses the annotation #MockBean it will force Spring to reload the context.
Bonus: you will see the same behavior if your test uses org.mockito.Mock.

In spring, is there a way to autowire the first bean?

In the example below, is there a way to avoid doing a context.getBean()? All the other beans subsequently used by the testService get autowired. (It is a console application)
public class Test {
private static ITestService testService;
private static ApplicationContext context;
public static void main(String[] args) {
context = new ClassPathXmlApplicationContext(
new String[]{"/META-INF/spring/app-context.xml"});
ITestService testService = context.getBean(ITestService.class);
}
}
I tried adding autowire annotation to ApplicationContext, but it didnt work. Besides how does it know where my app-context.xml is located if I autowire it?
Update: I found what I needed over here
Right, you're missing out a few details here.
Below is a short explanation of how Spring works.
1- The application context is loaded somehow (we will get there soon).
2- After loaded, app context will initialize/create all beans defined. Here is when beans get injected as dependencies. After this Whenever you get a bean back from the app context, that bean is all initialized and ready to go with all the dependencies in place (considering everything went fine).
RE the first step, there are a few way to automate the Spring initialization.
One way is what you are doing, explicitly instantiating one. Other way could be via a context listener in case you're in a web environment, or maybe with the #RunWith. (You can find more here)
In your case, I believe you are looking for using Spring in a (Unit?!?) test environment so you are looking for something like
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class MyTest {
#Autowired
private ApplicationContext applicationContext;
// class body...
}
further details here
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#testing
You cannot call beans without initializing the application context first.
Secondly in your case Test class should be bean itself to be managed by spring then to autowire ITestService. The purpose of Application context as a container is to manage the bean lifecycle so u need to initialize it first by ClassPathXmlApplicationContextand then it will initialize all beans declared by you in ur xml file. About avoiding the getBean method if you are using servlets for creating web app you can avoid getBean. If not you should handle it manually.
I agree with what #Desorder has said. When I started working with #RunWith(SpringJUnit4ClassRunner.class) and #ContextConfiguration, I used to get my test cases working. But it took me some time to understand how these two are working internally and their default configurations.
If you would like to take some different approach and would like to try without #RunWith and #ContextConfiguration, take a look at the link - TUTORIAL: JUNIT #RULE. With this, you will be very clear which spring xml file locations are provided.

Inject mocks into spring context WITHOUT spring trying to autowire the mock's fields

I have a class which I want to mock for a unit test. The class has a field with an #Autowired annotation. I need to inject that mock into a spring context that is being used in a junit. However, even though it's a mock, spring still sees the #Autowired and goes looking for an instance of this other class to inject. However, the context doesn't have an instance of that class, because one isn't needed (because the methods that might use that field are being mocked).
I've tried both XML and Java config, and in both cases the mock gets created and then spring still tries to find things to autowire into the mock (note I've set autowire=no in both cases).
XML that I've tried:
<bean class="org.easymock.EasyMock" factory-method="mock" autowire="no">
<constructor-arg value="com.stackoverflow.BeanClass" />
</bean>
Java config that I've tried:
#Bean(autowire = Autowire.NO)
public BeanClass beanClass1()
{
return EasyMock.createMock(BeanClass.class);
}
Note I've tried with both EasyMock and Mockito with the same results.
The only way I've found to do this is to do something like context.getBeanFactory().registerSingleton("name", mock) - but I can't do this if using SpringJUnit4ClassRunner.
Before anyone says "just don't do that", this is for an integration test rather than a pure unit test, and the complex wiring logic of some of the beans means that instantiating a spring context here saves a lot of duplicated effort in the test harness.
So, how can I stop spring trying to autowire beans into the fields of my mock?

Categories

Resources