Start JUnit test with fresh Spring Context - java

I wrote two unit test classes using JUnit4. They both run fine separately, but running them one after another (by for example mvn test), the second test fails.
The reason the second test fails is because the first test modified a bean in the first test. The second test wants to use a fresh instance of this bean.
A unit test should be given a new Context for each unit test class. Spring has first-class support for context caching which I would like to disable. How can I configure Spring to restart a new Context for each unit test class?
My test classes are configured as such:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:a.context.xml")
public class AUnitTest {
#Test
public void someTestMethod{
doSomeFancyStuff();
}
}

You can use #DirtiesContexton a test method (or a test class). The Spring ApplicationContext will be reloaded after the execution of the test.

You can also use Mockito.reset() after you test.
This will save you the loading time of the Spring context.

Related

I want to test a controller without using #SpringBootTest

I have the following test class:
#SpringBootTest
public class ChoreControllerTest
{
#Autowired
private ChoreController controller;
#Test
public void throwOnMissingChore()
{
assertThrows(ChoreNotFoundException.class, () -> this.controller.getChore(0L));
}
}
It takes about 5 seconds for Spring Boot to start up so the test can run. I want to reduce this time, but if I just remove the #SpringBootTest annotaton, I just get a NPE.
Is there a way to make this controller test more lightweight, or am I stuck with the startup time? I'm especially worried about what will happen to my test times if I ever want to test more than one controller....
The #SpringBootTest annotations create a Spring Context for you therefore it takes a while to start up. This annotation is mostly used for integration tests where a Spring context is required. Here are a few tips for optimizing integration tests.
If you remove the annotation the ChoreController cannot be autowired (no context available) which results in a NullpointerException.
Depending on your needs you can just use a Mocking library like Mockito to inject mocks e.g. services that your controller class needs and run the test without the #SpringBootTest.
You might want to take a look at this article for setting up those mocks properly.

What does it exactly mean to use #DirtiesContext annotation on a class?

I have been working with Spring and I came across the #DirtiesContext annotation provided by spring. Using it on every method makes sense when you want to refresh the application context before running each method(of course at the expense of performance) but what does it exactly mean to annotate a test class with #DirtiesContext? An example would be really helpful.
If you annotate a test class with #DirtiesContext, it will remove the application context after the tests are run. There is a lot more explaining in this article: DirtiesContext annotation
You can tell the Spring junit runner when to remove the application context with the classMode attribute.
For example if you mark a class with:
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
Then the application context will be cleared after each test method. You have more options also.
Other options for the class mode are the following:
AFTER_CLASS: The associated ApplicationContext will be marked as dirty after the test class.
AFTER_EACH_TEST_METHOD: The associated ApplicationContext will be marked as dirty after each test method in the class.
BEFORE_CLASS: The associated ApplicationContext will be marked as dirty before the test class.
BEFORE_EACH_TEST_METHOD: The associated ApplicationContext will be marked as dirty before each test method in the class.

Using MockitoJUnitRunner.class instead of SpringJUnit4ClassRunner.class

I have a question about the usage of SpringJUnit4ClassRunner. For pure Junits or Unit Test cases should we use Spring based annotations such as #Autowired along with SpringJUnit4ClassRunner or should we use only the MockitoJUnitRunner instead with the #RunWith annotation at the top of the Test class?
I mean replacing
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-applicationContext.xml" })
with just
#RunWith(MockitoJUnitRunner.class)
at the top of the class. It works for me.
In Junits we normally do not make any external calls such as calls to DB or call to some other web service. We have to mock these external calls using #Mock annotations on this service objects. And then create a real object of the class that we are testing and that depends on these mocks. We can then use #InjectMocks on the real object so that it will be injected with the mocked objects.
Example Service-A->Calls->Service-B->Calls->Service-C
While testing A we should mock Service B & while testing Service-B we should mock Service-C.
Some code Snippet
#RunWith(MockitoJUnitRunner.class)
public class TestServiceA {
#Mock
B mockObj;
#InjectMocks
A realObj;
#Test
public void testServiceA() {
.....
.....
}
}
So, I feel for Unit test cases we need not rely on Spring container to provide us the instance of the class we are testing.
Please give your suggestions.
Using SpringJUnit4ClassRunner.class instead of MockitoJUnitRunner.class
If you try to simply unit test a class without the dependencies, like you describe, there is no need for the SpringJUnit4ClassRunner. This runner is able to generate a complete Spring context with (mock) objects you can define in your (test) application context configuration. With this mechanism the SpringJUnit4ClassRunner is much slower than the regular MockitoJUnitRunner.
The SpringJUnit4ClassRunner is very powerful for integration test purposes.
I default start with the MockitoJUnitRunner and if I reach the limits of this runner, for instance because I need to mock constructors, static methods or private variables, I switch to PowerMockJUnitRunner. This is for me a last resort as it normally tells the code is ‘bad’ and not written to be tested. Other runners are normally not necessary for isolated unit tests.
Building on Sven's answer, Let us say that you needed to test an assembly of classes while mocking out bits that go to the database, or call an external service, you would look to run the test with SpringJUnit4ClassRunner.
If you were trying to test a Single Java Class as a Unit, mocking out both the integration bits and local collaborators, then running the test with MockitoJUnitRunner is sufficient and faster as well.

Problems autowiring beans to TestNG test when using spring profiles

I am currently polishing a test framework we have. For the current needs, we must support multiple spring profiles, and run our tests multiple times, each time with a different profile. Each profile targets separate test environments, and thus different sets of tests, with different logic may be executed.
I am having a test class like this:
#ContextConfiguration(locations = { "classpath:META-INF/test-context.xml" })
public class Test extends AbstractTestNGSpringContextTests {
#Autowired
ProfileSpeciticBean profileSpecificBean;
...
}
Here, ProfileSpecificBean is an interface, that is implemented by separate classes. The actual implementation to be injected is determined by the active Spring profile, and I am using Spring XML contexts. I am building the project with Maven, using the -Dspring.profiles.active=profileName command, thus expecting the tests to catch the passed profile.
However, the current test fails with this error within the full stacktrace:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type ProfileSpeciticBean found for dependency: expected at least 1 bean which qualifies as autowire candindate, found 0
After some reaearch on this topic, I found that the AbstractTestNGSpringContextTests expects an #ActiveProfiles annotation on top of the test class. So, this code works:
#ContextConfiguration(locations = { "classpath:META-INF/test-context.xml" })
#ActiveProfiles("profile1")
public class Test extends AbstractTestNGSpringContextTests ...
The problem with this is: I want to avoid hard-coding the profile name in my classes. I need to run the same test class for different profiles, by only altering the command-line script.
Is the above possible? Is there any way to make TestNG aware of the command-line profile, and re-use the same test? I need to avoid both duplicating code and configuration to make my tests run, so making two Test classes for each profile is not what I want.
Try following How to set JVM parameters for Junit Unit Tests? to set the system variables for the VM that actually runs the tests - it's not the same one as the one that runs maven.
Set your profile there.
You can use a maven system parameter to set that from the invocation of maven (or use maven profiles).
To get more precise answer I suggest you add stacktrace and the piece of you main configuration (where you declared beans that is supposed to be replaced by test beans).
Here is the general idea:
let's say you want to change PropertyPlaceholderConfigurer depending on your profile.
Steps:
You create you main-config.xml
that contains PropertyPlaceholderConfigurer and mark it with profile="default"
You create you test-config.xml with test implementation of PropertyPlaceholderConfigurer
(don't forget to mark test-config.xml with profile="MyTestProfile" or mark only PropertyPlaceholderConfigurer with profile="MyTestProfile)
Than you import both test-config.xml and main-config.xml to your tests
#ContextConfiguration(locations = { "classpath:META-INF/main-config.xml","classpath:META-INF/test-config.xml" })
#ActiveProfiles("MyTestProfile")
public class Test extends AbstractTestNGSpringContextTests {}
It should work. Good luck.

Mocked Objects in Context during tests execution

I'm working on an application which has both component and integration tests. The differences between then is: a component test test more than one class (i.e., its inner objects aren't all mocked out - but some of them might be [such as JMS publishers]) and an Integration test is a test that nothing at all is mocked out. In another words, Spring gives you the object and you test it as it is.
So far, so good.
The problem is: in order to be able to replace one dependency or another from the Spring context, I used Springockito (https://bitbucket.org/kubek2k/springockito/wiki/Home) which offers you a way to mock out some bean from the Spring context.
So - in the component tests - I have this:
#RunWith(SpringJUnit4ClassRunner.class)
#DirtiesContext(classMode = AFTER_CLASS)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = "classpath:spring-classify-test.xml")
public class....
#Autowired
#ReplaceWithMock
private SomeServiceInterface someServiceInterface;
#Autowired
private Bean bean;
Bean has SomeServiceInterface as a depedency.
public class Bean {
private SomeServiceInterface...
In the case above, SomeServiceInterface will be replaced by a mock. Of course, that example is an oversimplification of the problem - I replace bean with mock objects that are dependecies further down in the object graph.
It's worthy noticing that I load the context from this file: spring-classify-test.xml Also it's wothy noticing that I mark the context as dirty after the execution of the class - so, AFAIK, the next test class must reload the context.
Now the integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = SpringockitoContextLoader.class, locations = {"classpath:/spring-service-integration-test.xml" })
public class ...
#Autowired
private Bean bean;
I load the context from spring-service-integration-test.xml - but SomeServiceInterface inside of Bean is still mocked! The context used in the integration test was changed as well!
If I mark the Integration test with #DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD), the first test in the class will fail because SomeServiceInterface is mocked - but the next test will pass because the context has been refreshed already.
Funny thing is:
If I ask Spring to inject SomeServiceInterface in the Integration Test, it will inject a SomeServiceInterface concrete implementation - not a mock!
I have tried many things to sort out that issue:
Programatically override the beans in the context after the component tests are done using the registerBeanDefinition method from the context
Create a TestExecution listener so I could try to manually refresh the context before the execution of an IntegrationTest
Use the same loader for the different contexts....
This story goes on and on.
Does anyone have any idea?
P.S.: I quite understand that adopting Springockito was a dubious idea - but that decision was not made by me and now we have over 500 tests in the project - hence refactoring them all to remove Springockito will be a lenghty task, therefore it is not a viable option ATM.
The #DirtiesContext annotation is handled by the DirtiesContextTestExecutionListener when it's registered with the TestContextManager. With plain vanilla Spring tests, that listener is registered by default. Perhaps Springockito or something else in your test "component test" is doing something that interferes with the default listener registration?
I suggest to try latest springockito-annotations 1.0.8 with
#DirtiesMocks(classMode = DirtiesMocks.ClassMode.AFTER_CLASS)
See https://bitbucket.org/kubek2k/springockito/wiki/springockito-annotations#!reseting-mocksspies-in-experimental-package-hope-youll-like-it.

Categories