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.
Related
We have a bean CompanyProcessor.
In spring boot integration test, we want to test an exception scenario when a method in this bean throws Exception.
Therefore we have created an overriding bean TestCompanyProcessor which extends CompanyProcessor.
TestCompanyProcessor is marked #Primary. When we run just this test class, the test runs fine and the overridden bean is used as expected.
But then this test class is run with several other test classes together, each marked as #SpringBootTest, we see that this test fails at first instance as instead of using the overridden bean, it uses this original bean. We have automatic retries on failure for upto 3 retries. On the second or third retry, it somehow finds the correct overridden primary bean and the test passes.
Is there any reason, why it finds the original bean at first instance and then finds the correct overridden bean on subsequent retries when run alongside several other test classes.
The overriden bean is defined as follows.
#Primary
#Component
#ConditionalOnExpression("'${test.company.processor}' == 'true'")
class TestCompanyProcessor
and on the test class we have
#TestPropertySource(properties = {"test.company.processor=true"})
class TestCompanyProcessorTest {
}
P.S.
we saw the same behaviour when we used #Mockbean annotation
You sees this behaviour probably if you have multiple Spring tests that share the same context.
Spring is caching your test application context between tests. This is the default behaviour as the instanciation of the objects in a spring context may take some time.
If you have different beans in different tests you should mark tests that change the ApplicationContext (like the TestCompanyProcessorTest class) with an #DirtiesContext annotation.
Further info on caching:
https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testing-ctx-management
DirtiesContext example:
https://www.baeldung.com/spring-dirtiescontext
I have a test class that uses #TestPropertySource to load a hsqldb.
Essentially, the test file has gotten quite large, and I would like to break it up a bit. The problem is that loading the DB takes... some time. Not too much, but I don't want to create several test files that each load up the DB.
My code looks like this:
#TestPropertySource(properties = {
"hsqldb.name=SettingsTest"
})
#ContextConfiguration(classes = { settings.config.Config.class }, loader = AnnotationConfigContextLoader.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
public class SvcTest
.
.
.
This will run some functions to load up the database. It is my understanding that once the test file is done with all its tests, it will stop the DB. How can I keep it running so other files can use the DB, and close it only when they are done?
Attempting to cache the application context between test classes while at the same time marking the tests as "dirty" using #DirtiesContext is a bit contradictory:
DirtiesContext: Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache.
If you use the annotation solely to reset the state of your database, you could instead create a ClassRule to manually reset the test data in the database instead of tearing down and rebuilding the full application context.
Furthermore, if the tests are focused on the repository section of the application, Spring offers test slicing. Test slices load a small fragment of the application context, thereby reducing the load time. An example is the #JdbcTest annotation, used for JDBC tests that focus on JDBC-based components.
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.
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.
Note that I'm mirroring the example given here very closely.
In fact, my situation is somewhat simpler as I'm not even testing with a persistence unit at this point. My test project provides a simple MDB and a session bean; both the MDB and the session bean are getting loaded as normal, and can be successfully tested (in a constrained fashion) without injection.
The suggested injection with the #LocalClient annotation on my unit tests is failing with the known error:
javax.naming.NamingException: Unable to find injection meta-data for [your-class]. Ensure that class was annotated with #org.apache.openejb.api.LocalClient and was successfully discovered and deployed. See http://openejb.apache.org/3.0/local-client-injection.html
When I visit this page it informs me that I may need to add an extra property to my test case context setup. So that now looks like:
#Override
public void setUp() throws Exception {
initializeContext();
}
public void initializeContext() {
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
// the property i've added
p.put("openejb.tempclassloader.skip", "annotations");
try {
InitialContext initialContext = new InitialContext(p);
initialContext.bind("inject", this);
} catch (Throwable throwable) {
throwable.printStackTrace();
throw new RuntimeException(throwable);
}
}
But it's still failing. I really like this idiom and would be very excited if I could successfully use it in my projects.
A few other notes:
I am providing an 'empty' ejb-jar.xml (in src/main/resources) and an application-client.xml (in src/test/resources) as suggested by Apache to tell OpenEJB to scan the classpath [UPDATE: as it turns out, I was doing this wrong. See my answer below for the suggestion that worked for me.]
The test cases annotated with #LocalClient aren't identified by the OpenEJB engine as actually getting picked up and processed properly (as my MDBs are, for example)
Thanks in advance for any help or guidance.
This issue is likely caused by improper location of the descriptors which hint OpenEJB which sorts of modules are available.
To ensure the test-classes get picked up properly, make sure you're placing a file named application-client.xml at src/test/resources/META-INF with the following content:
<application-client/>
This should force OpenEJB to scan and react to the presence of #LocalClient annotations.
I had a similar issue when I tried to test stuff in a test project called tomee-embedded-trial and it turned out that openejb ignores stuff called tomee-.* .
I fixed it for me by specifying the following system properties:
openejb.deployments.classpath.include=".*-trial.*" openejb.deployments.package.include=".*-trial.*"