Reload Spring application context after every test - java

I have a test Class which contains 2 test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:applicationContextTest.xml" })
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class MyITest extends implements BeanFactoryAware {
private BeanFactory beanFactory;
#Test
public void test1() throws Exception {}
#Test
public void test2() throws Exception {}
}
When I run tests individually I get no errors, but when I run all tests together there is a failure. This failure is due to some tests modifying the application context:
b = beanFactory.getBean("logDataSource", BasicDataSource.class);
b.set ...
Is there an option to run this test separately? I just want when test1 start it read all necessary things then run test and then close all necessary things. And then start test2.

You can use the #DirtiesContext annotation on the test class that modifies the application context.
Java Doc
Spring documentation
By default, this will mark the application context as dirty after the entire test class is run. If you would like to mark the context as dirty after a single test method, then you can either annotate the test method instead or set the classMode property to AFTER_EACH_TEST_METHOD at your class level annotation.
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)

Related

Injecting non-mocks defined by a #Configuration class into a unit test

Java 8, JUnit 4 and Spring Boot 2.3 here. I have a situation where I have a #Component-annotated Spring Boot class that gets #Autowired with all its dependencies (the beans are defined in a #Configuration-annotated config class):
#Configuration
public class SomeConfig {
#Bean
public List<Fizz> fizzes() {
Fizz fizz = new Fizz(/*complex logic here*/);
return new Collections.singletonList(fizz);
}
#Bean
public Buzz buzz() {
return new Buzz(/*complex logic here*/);
}
}
#Component
public class ThingDoerinator {
#Autowired
private Lizz<Fizz> fizzes;
#Autowired
private Buzz buzz;
public String doStuff() {
// uses fizz and buzz extensively inside here...
}
}
I can easily write a unit test to inject all of these dependencies as mocks:
public class ThingDoerinatorTest extends AbstractBaseTest {
#Mock
private List<Fizz> fizzes;
#Mock
private Buzz buzz;
#InjectMocks
private ThingDoerinator thingDoerinator;
#Test
public void should_do_all_the_stuff() {
// given
// TODO: specify fizzes/buzz mock behavior here
// when
String theThing = thingDoerinator.doStuff();
// then
// TODO: make some assertions, verify mock behavior, etc.
}
}
And 80% of the time that works for me. However I am now trying to write some unit tests that are more like integration tests, where instead of injected mocks, I want the beans to be instantiated like normal and get wired into the ThingDoerinator class like they would be in production:
public class ThingDoerinatorTest extends AbstractBaseTest {
#Mock
private List<Fizz> fizzes;
#Mock
private Buzz buzz;
#InjectMocks
private ThingDoerinator thingDoerinator;
#Test
public void should_do_all_the_stuff() {
// given
// how do I grab the same Fizz and Buzz beans
// that are defined in SomeConfig?
// when -- now instead of mocks, fizzes and buzz are actually being called
String theThing = thingDoerinator.doStuff();
// then
// TODO: make some assertions, verify mock behavior, etc.
}
}
How can I accomplish this?
You can use SpringBootTest.
#SpringBootTest(classes = {Fizz.class, Buzz.class, ThingDoerinator.class})
#RunWith(SpringRunner.class)
public class ThingDoerinatorTest {
#Autowired
private Fizz fizz;
#Autowired
private Buzz buzz;
#Autowired
private ThingDoerinator thingDoerinator;
}
You don't need to mock anything, just inject your class in your test and your configuration will provide the beans to your ThingDoerinator class and your test case will work as if you are calling the ThingDoerinator. doStuff() method from some controller or other service.
TL;DR
I think, you are confused with tests running whole spring context and unit test which just test the function/logic we wrote without running whole context. Unit tests are written to test the piece of function, if that function would behave as expected with given set of inputs.
Ideal unit test is that which doesn't require a mock, we just pass the input and it produces the output (pure function) but often in real application this is not the case, we may find ourselves in situation when we interact with some other object in our application. As we are not about to test that object interaction but concerned about our function, we mock that object behaviours.
It seems, you have no issue with unit test, so you could mock your List of bean in your test class ThingDoerinator and inject them in your test case and your test case worked fine.
Now, you want to do the same thing with #SpringBootTest, so I am taking a hypothetical example to demonstrate that you don't need to mock object now, as spring context will have them, when #springBootTest will load whole context to run your single test case.
Let's say I have a service FizzService and it has one method getFizzes()
#Service
public class FizzService {
private final List<Fizz> fizzes;
public FizzService(List<Fizz> fizzes) {
this.fizzes = fizzes;
}
public List<Fizz> getFizzes() {
return Collections.unmodifiableList(fizzes);
}
}
Using constructor injection here1
Now, my List<Fizzes would be created through a configuration (similar to your case) and been told to spring context to inject them as required.
#Profile("dev")
#Configuration
public class FizzConfig {
#Bean
public List<Fizz> allFizzes() {
return asList(Fizz.of("Batman"), Fizz.of("Aquaman"), Fizz.of("Wonder Woman"));
}
}
Ignore the #Profile annotation for now2
Now I would have spring boot test to check that if this service will give same list of fizzes which I provided to the context, when it will run in production
#ActiveProfiles("test")
#SpringBootTest
class FizzServiceTest {
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).isNotNull().isNotEmpty().hasSize(3);
assertThat(fizzes).extracting("name")
.contains("Wonder Woman", "Aquaman");
}
}
Now, when I ran the test, I saw it works, so how did it work, let's understand this. So when I run the test with #SpringBootTest annotation, it will run similar like it runs in production with embedded server.
So, my config class which I created earlier, will be scanned and then beans created there would be loaded into the context, also my service class annotated with #Service annotation, so that would be loaded as well and spring will identify that it needs List<Fizz> and then it will look into it's context that if it has that bean so it will find and inject it, because we are supplying it from our config class.
In my test, I am auto wiring the service class, so spring will inject the FizzService object it has and it would have my List<Fizz> as well (explained in previous para).
So, you don't have to do anything, if you have your config defined and those are being loaded and working in production, those should work in your spring boot test the same way unless you have some custom setup than default spring boot application.
Now, let's look at a case, when you may want to have different List<Fizz> in your test, in that case you will have to exclude the production config and include some test config.
In my example to do that I would create another FizzConfig in my test folder and annotate it with #TestConfiguration
#TestConfiguration
public class FizzConfig {
#Bean
public List<Fizz> allFizzes() {
return asList(Fizz.of("Flash"), Fizz.of("Mera"), Fizz.of("Superman"));
}
}
And I would change my test a little
#ActiveProfiles("test")
#SpringBootTest
#Import(FizzConfig.class)
class FizzServiceTest {
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).extracting("name")
.contains("Flash", "Superman");
}
}
Using different profile for tests, hence #ActiveProfile annotation, otherwise the default profile set is dev which will load the production FizzConfig class as it would scan all the packages ( 2Remember the #Profile annotation above, this will make sure that earlier config only runs in production/dev env ). Here is my application.yml to make it work.
spring:
profiles:
active: dev
---
spring:
profiles: test
Also, notice that I am importing my FizzConfiguration with #Import annotation here, you can also do same thing with #SpringBootTest(classes = FizzConfig.class).
So, you can see test case has different values than in production code.
Edit
As commented out, since you don't want test to connect to database in this test, you would have to disable auto configuration for spring data JPA, which spring boot by default does that.
3You can create a another configuration class like
#Configuration
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public TestDataSourceConfiguration {}
And then include that with your #SpringBootTest, this will disable default database setup and then you would be left with last piece of puzzle which is any repository interaction you might be having in your class.
Because now, you don't have auto configuration in your test case, those repository are just interfaces, which you can easily mock inside your test class
#ActiveProfiles("test")
#SpringBootTest
#Import(FizzConfig.class, TestDataSourceConfiguration.class)
class FizzServiceTest {
#MockBean
SomeRepository repository;
#Autowired
private FizzService service;
#Test
void shouldGiveAllTheFizzesInContext() {
// Use Mockito to mock behaviour of repository
Mockito.when(repository.findById(any(), any()).thenReturn(Optional.of(new SomeObject));
List<Fizz> fizzes = service.getFizzes();
assertThat(fizzes).extracting("name")
.contains("Flash", "Superman");
}
}
1Constructor injection is advisable instead of `#Autowired` in production code, if your dependencies are really required for that class to work, so please avoid it if possible.
3Please note that you create such configuration only in test package or mark with some profile, do not create it in your production packages, otherwise it will disable database for your running code.

Unit testing code inside TransactionSynchronizationManager

I couldn't find a working solution, how to unit test something that happened inside TransactionSynchronizationManager.registerSynchronization(). I have code like this:
pubclic class MyService {
public void method() {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter()
{
#Override
public void afterCommit()
{
myService.callSmth(params);
}
}
}
}
Here I call method():
public class MyClass {
private MyService myService;
public void method2() {
myService.method();
}
}
In my unit test I need to verify that myService.callSmth. But seems like test code doesn't go into afterCommit() method.
That's my test class:
#Configurable
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath*:test-applicationContext.xml" })
public class ServiceTest extends TestHelper
{
#Mock
MyService myService;
#InjectMocks
MyClass myClass;
#Test
public void allowWriteOffGlobal()
{
myClass.method();
veryfy(myService).method();
}
}
I simplified my code and please, don't pay attention if there could be any mistakes in mocking and smth like this. In a debugger I see how the flow comes to line TransactionSynchronizationManager.registerSynchronization and skips the afterCommit() method.
P.s.: not sure I provided enough info. Please, write in the comment if there is a need of smth extra. Thanks.
From Spring Test documentation (emphasis mine):
Annotating a test method with #Transactional causes the test to be run within a transaction that will, by default, be automatically rolled back after completion of the test. If a test class is annotated with #Transactional, each test method within that class hierarchy will be run within a transaction. Test methods that are not annotated with #Transactional (at the class or method level) will not be run within a transaction. Furthermore, tests that are annotated with #Transactional but have the propagation type set to NOT_SUPPORTED will not be run within a transaction.
and
By default, test transactions will be automatically rolled back after completion of the test; however, transactional commit and rollback behavior can be configured declaratively via the #Commit and #Rollback annotations at the class level and at the method level.
So I suggest putting a #Commit annotation in your test method. (By the behaviour you describe I suppose that you are using Spring Test, this advice does not apply otherwise).
Try mocking TransactionSynchronizationAdapter and call afterCommit()

Spring dependencies not being injected into BeforeSuite method?

I am running a spring boot application that uses TestNG as the testing framework. My tests are set up like this:
A parent class, which is in charge of setup logic and takes care of all of the configuration stuffs:
#ContextConfiguration(classes = {TestingConfig.class}, initializers = ConfigFileApplicationContextInitializer.class)
#ContextConfiguration(classes = TestConfig.class)
#TestPropertySource(locations = "classpath:application.yml")
public abstract ParentTestClass extends AbstractTestNGSpringContextTests {
#Autowired
private ServiceClient serviceClient;
#BeforeSuite
public void beforeClass() {
Assert.assertNotNull(serviceClient);
serviceClient.doSomeSetupWork();
}
}
There are multiple child test classes. Each on inherits form the parent test class so that they share the same setup logic.
public ChildTestClass1 extends ParentTestClass {
#Test
public void someTest() {
...
}
// More tests not shown
}
public ChildTestClass2 extends ParentTestClass {
#Test
public void anotherTest() {
...
}
// More tests not shown
}
The serviceClient is a client for one of the web services that the test suite depends on. I am making calls with the service client to set up the data in the other service before running the test cases.
The problem is this: previously I was using the #BeforeClass annotation, which meant that the parent class's setup method was being run once for every child test class. This was ok, but it was really slow waiting for the same setup to be run multiple times.
So I thought to myself: I'll just change the #BeforeClass annotations in the ParentTestClass to be #BeforeSuite instead! That will solve all of my problems!
Wrong.
Now when I run it, the Assert.assertNotNull(serviceClient); line in the beforeClass() method of the parent class fails. In short, Spring dependencies aren't being injected into the #BeforeSuite annotated method, even tho they were being injected in the method when it was annotated with #BeforeClass.
Any thoughts here? I'd really appreciate it!
I believe this is working as designed. From looking at how the implementation is built within org.springframework.test.context.testng.AbstractTestNGSpringContextTests (from which you extend), the dependencies are injected into your test class via the org.springframework.test.context.support.DependencyInjectionTestExecutionListener#injectDependencies (this is a listener).
All the listeners including the DependencyInjectionTestExecutionListener is invoked only via org.springframework.test.context.testng.AbstractTestNGSpringContextTests#springTestContextPrepareTestInstance which is a #BeforeClass(alwaysRun=true) classified method.
So your dependencies aren't available to you until and unless this #BeforeClass annotated method runs to completion. So you would have to move out your #BeforeSuite method and have it work with a #BeforeClass annotation only.
If you don't need your service setup to be done multiple times, then you would need to add an edit check in your test code that does the setup ONLY IF ITS NOT DONE.
Here is the solution to all your problem.
#Override
#BeforeSuite
protected void springTestContextPrepareTestInstance() throws Exception {
super.springTestContextPrepareTestInstance();
}
Hope this helps you to inject the dependencies.

Spring: #BootstrapWith for ApplicationContext

Currently I use #BootstrapWith annotation in conjunction with custom class, which is simply sets some system properties used in test. However (as far as I understand it) those properties are being set each time TestContextManager instantiates test and TestContext with it:
#BootstrapWith is a class-level annotation that is used to configure
how the Spring TestContext Framework is bootstrapped
spring.io
Is there any way to set properties once before ApplicationContext is started?
Edit:
I cannot use #RunWith(SpringJUnit4ClassRunner.class) due to parameterized tests, which require #RunWith(Parameterized.class). I use SpringClassRule and SpringMethodRule instead
Additionally I run not only parameterized tests, but ordinary tests as well. Thus I cannot simply extend Parameterized runner
I think that the most basic way of setting some properties before ApplicationContext sets up is to write custom runner, like that:
public class MyRunner extends SpringJUnit4ClassRunner {
public MyRunner(Class<?> clazz) throws InitializationError {
super(clazz);
System.setProperty("sample.property", "hello world!");
}
}
And then you can use it instead your current runner.
#RunWith(MyRunner.class)
#SpringBootTest
//....
If your current runner seems to be declared final, you can possibly use aggregation (but I have not tested in) instead of inheritance.
Here you can find a sample Gist where this runner is used and property gets successfully resolved.
update
If you don't want to use custom Runner (although you could have several runners setting properties for each case - with parameters, without parameters, and so on). You can use #ClassRule which works on static fields/methods - take a look at the example below:
#ClassRule
public static ExternalResource setProperties() {
return new ExternalResource() {
#Override
public Statement apply(Statement base, Description description) {
System.setProperty("sample.property", "hello world!");
return super.apply(base, description);
}
};
}
This static method is to be placed in your test class.
If it's just Properties that you want to add as part of the bootstrapping of the spring application context you can use the annotation #TestPropertySource at the top of your Junit test class like this...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={SpringConfig.class,SpringConfigForTests.class})
#TestPropertySource(properties = "runDate=20180110")
public class AcceptanceTests {
...
I use this technique to load my production SpringConfig class (which loads properties files), then override some beans specifically for testing in SpringConfigForTests (and potentially loading test specific properties files), then adding the another run-time property named runDate.
I am using Spring 4.2.5.RELEASE and it looks like this annotation has been included since 4.1
I discovered that this is possible with extending AnnotationConfigContextLoader (or any other ContextLoader) and overriding prepareContext() method:
#Override
protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
// set properties here
super.prepareContext(context, mergedConfig);
}
Than custom loader should be specified in #ContextConfiguration annotation on test instance
If you are ultimately consuming the program arguments as an ApplicationArguments then you can simply provide an ApplicationArguments in the test context. It will be in conflict with the empty-argument one provided via #SpringBootTest but you can force yours to be used by annotating it #Primary.
#Component
public class ArgReadingBean {
public ArgReadingBean(ApplicationArguments arguments) {
// ...
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { TestConfig.class, ArgDependentBean.class })
public class AppArgsTest {
#Resource
private ArgReadingBean bean;
public static class TestConfig {
#Bean
#Primary
ApplicationArguments myArguments() {
return new DefaultApplicationArguments("-help");
}
}
#Test
public void testArgs() {
assertThat(bean).isNotNull(); // or whatever
}
}

Best way to exclude unit test #Configurations in Spring?

In my Spring Boot project, I have a main class annotated with #SpringBootConfiguration. I also have some unit tests that use #SpringApplicationConfiguration that points to an inner class that defines a Spring context for usage in my unit test (using some mocks).
I now want to write an integration test that starts the full context of my application. However, this does not work as it also picks up the Spring contexts that are defined as inner classes in other unit tests.
What would be the best way to avoid that? I did see the exclude and excludeName properties on #SpringBootConfiguration, but I am unsure how to use them.
UPDATE:
Some code to explain the problem more:
My main class:
package com.company.myapp;
#SpringBootApplication
#EnableJpaRepositories
#EnableTransactionManagement
#EntityScan(basePackageClasses = {MyApplication.class, Jsr310JpaConverters.class})
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I have a unit test for Spring REST Docs:
package com.company.myapp.controller
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration
#WebAppConfiguration
public class SomeControllerDocumentation {
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
// Some test methods here
// Inner class that defines the context for the unit test
public static class TestConfiguration {
#Bean
public SomeController someController() { return new SomeController(); }
#Bean
public SomeService someService() { return new SomeServiceImpl(); }
#Bean
public SomeRepository someRepository() { return mock(SomeRepository.class);
}
So the unit test uses the context defined in the inner class. Now I want a different test that tests if the "normal" application context of the app starts up:
package com.company.myapp;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(MyApplication.class)
#WebAppConfiguration
public class MyApplicationTest {
#Autowired
private ApplicationContext applicationContext;
#Test
public void whenApplicationStarts_thenContextIsInitialized() {
assertThat(applicationContext).isNotNull();
}
}
This test will now not only wire the stuff it should, but also the beans from the SomeControllerDocumentation.TestConfiguration inner class. This I want to avoid.
You could use profiles: annotate the relevant configuration beans with #Profile("unit-test") and #Profile("integration-test") and inside the tests specify which profile should be active via #ActiveProfiles.
However, it sounds like you could avoid the problem altogether just by reorganising your configuration structure. It's hard to assume anything without seeing any code, though.
Since Spring Boot 1.4, the problem can be avoided by annotation the configuration in the unit tests with #TestConfiguration
I think you talk about #Ignore

Categories

Resources