Spring Unit Test and object re-injection - spring

In Spring what is the best way to run a bunch of test methods on an object but reset/re-inject the object before each new test method invocation?
I have tried to do in the code below but with my current logic the object gets created and injected only once..
package com.bidtracker;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.is;
import com.bidtracker.iface.BidTracker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class BidTrackerTest {
#Autowired
BidTracker tracker;
#Test
public void shouldReturnHighestBidAmount1(){
tracker.bidOnItem("itemB", "user1", 105);
assertThat(tracker.getHighestBid("itemB").getAmount(),is(Integer.valueOf(105)));
}
#Test
public void shouldReturnHighestBidAmount2(){
tracker.bidOnItem("itemB", "user1", 39);
tracker.bidOnItem("itemB", "user1", 50);
assertThat(tracker.getHighestBid("itemB").getAmount(),is(Integer.valueOf(50)));
}
}

The issue here is that Spring test support by default caches the application context and will reuse the cached context if elements of the key match up(context name(s), active profiles etc). To ask Spring to remove the context from the cache you can mark the test with #DirtiesContext annotation (at the method level or test class level).

JUnit has the #Before annotation, you can use that to do any initialization before each test.
However, I am curious about your case. Here's from the #Test documentation:
The Test annotation tells JUnit that the public void method to which
it is attached can be run as a test case. To run the method, JUnit
first constructs a fresh instance of the class then invokes the
annotated method. Any exceptions thrown by the test will be reported
by JUnit as a failure. If no exceptions are thrown, the test is
assumed to have succeeded.
I'm guessing that the issue is that the bean is a singleton. It actually gets injected again, but if it's been modified you're using the same thing. You can try the #DirtiesContext annotation.

Related

How do I annotate my JUnit test so it will run the way my Spring Boot app runs?

I have a Spring Boot web application that I launch by running this class ...
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The web app has a JSP/HTML front end served up by Spring MVC Controllers which talk to Services which talk to DAOs which uses Hibernate to read/write Entities to a MySQL database.
All the components and services get instantiated and #Autowired and the web app runs fine.
Now, I want to build JUnit tests and test some of the functionality in the Services or the DAOs.
I started writing a JUnit test like below, but I quickly got stuck on not knowing how to instantiate all the #Autowired components and classes.
public class MySQLTests {
#Test
public void test000() {
assertEquals("Here is a test for addition", 10, (7+3));
}
#Autowired
UserService userService = null;
#Test
public void test001() {
userService.doSomething("abc123");
// ...
}
}
I basically want the web application to start up and run, and then have the JUnit tests run the methods in those Services.
I need some help getting started ... is there some kind of JUnit equivalent of the #SpringBootApplication annotation that I can use in my JUnit class?
Answering my own question ... I got it working like this ...
Annotated the test class with:
#RunWith(SpringRunner.class)
#SpringBootTest
The test class had to be in a package above the #Controller class ... so my test class is in com.projectname.* and the controller is is com.projectname.controller.*
The working code looks like this ...
package com.projectname;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.projectname.controller.WebController;
#RunWith(SpringRunner.class)
#SpringBootTest
public class Test1 {
#Autowired
private WebController controller;
#Test
public void contextLoads() throws Exception {
assertNotNull("WebController should not be null", controller);
}
}

#Autowired should not work without #RunWith(SpringRunner.class) but does

Here is a unit testing class for a java spring data repository layer.
I have a spring data repository layer in which the annotation #Autowired is used to inject TestEntityManager type object (belongs to spring data package).
The autowiring works whitout adding #RunWith(SpringRunner.class) annotation !
So what is the problem ? Well, I think that injection should not be possible whitout adding #RunWith(SpringRunner.class) annotation to the class : it should not work without it theorically.
How is it possible ? Does someone have any answer ?
>>>> view complete spring boot app code on github available here
Someone else have had the opposite problem in stackoverflow :
Someone else have had the opposite problem : see link here
Here is my strange bloc of code that amazingly that works :
package org.loiz.demo;
import org.assertj.core.api.BDDAssertions;
import org.junit.Assert;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test ;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Order ;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import demo.LoizBootSpringDemoApplication;
import demo.crepository.UserRepositoryInterface;
import demo.dmodel.User;
import demo.helper.helperUtils;
//Test de la couche de persistence
//#RunWith(SpringRunner.class)
#DataJpaTest
#ContextConfiguration(classes = { LoizBootSpringDemoApplication.class})
#TestMethodOrder(MethodOrderer.OrderAnnotation.class)
#SuppressWarnings("unused")
public class LoizPersistenceTest
{
#Autowired
private TestEntityManager testEntityManager;
#Autowired
private UserRepositoryInterface repository;
private static Long idStub ;
#Test
#Order(1)
#Rollback(false)
#DisplayName("Test de sauvegarde d\'un user \"prenom21 Nom21\"")
public void saveShouldMapCorrectly() throws Exception {
User userStub = new User("prenom21", "Nom21");
User UserSaved = this.testEntityManager.persistFlushFind(userStub);
BDDAssertions.then(UserSaved.getId()).isNotNull();
idStub = UserSaved.getId() ;
User UserRead = this.testEntityManager.find(User.class, idStub) ;
BDDAssertions.then(UserSaved.getFirstName()).isNotBlank();
BDDAssertions.then(UserSaved.getFirstName()).isEqualToIgnoringCase("prenom21");
BDDAssertions.then(UserSaved.getLastName()).isEqualToIgnoringCase("Nom21");
BDDAssertions.then(UserSaved.getLastName()).isNotBlank();
}
#Test
#Order(2)
#DisplayName("Test d'existence du user \"prenom21 Nom21\"")
public void readShouldMapCorrectly() throws Exception {
User userStub = new User(idStub, "prenom21", "Nom21");
User userFetched = this.testEntityManager.find(User.class, idStub) ;
String sUserStub = userStub.toString() ;
String sUserFetched = userFetched.toString() ;
boolean bolSameObject = userStub.equals(userFetched) ;
boolean bolAttrEgalEgal = sUserStub == sUserFetched ;
boolean bolStringEqual = sUserStub.equals(sUserFetched) ;
boolean bBeanUtil = helperUtils.haveSamePropertyValues (User.class,userStub,userFetched) ;
Assert.assertTrue(bBeanUtil);
}
}
Maybe it is normal, but what do i have to know ?
I would appreciate some answer please :)
>>>> view complete spring boot app code on github available here
#RunWith(SpringRunner.class) is used for junit 4 test.
But in our case, it is Junit 5 that is used. That is what we can see reading at the pom.xml file (using of junit.jupiter artifact proves that).
So #RunWith(SpringRunner.class) has no effects to manage spring container.
it should be replaced by #ExtendWith(SpringExtension.class).
HOWEVER :
#DataJpaTest already "contains" #ExtendWith(SpringExtension.class) !!!
So we don't need to add #ExtendWith(SpringExtension.class) when #DataJpaTest is used.
#DataJpaTest will allow to test spring data repository layer but will also guarantee spring dependency injection.
That is to say, and roughly speeking, you can use #Autowired annotation for a class which is annotated #DataJpaTest (a spring data repository layer).
From imports junit.jupiter i can see you are using junit-5, were #RunWith belongs to junit-4, and for reference #ExtendWith is not mandataroy for junit-5 test and if you want to use a specific runner you still require the #ExtendsWith but as #DataJpaTest itself is annotated with #ExtendsWith you don't need to do this explicitly
In JUnit 5, the #RunWith annotation has been replaced by the more powerful #ExtendWith annotation.
To use this class, simply annotate a JUnit 4 based test class with #RunWith(SpringJUnit4ClassRunner.class) or #RunWith(SpringRunner.class).

Clear Spring application context after test

How can I clear the application context after each test execution, with Junit5 and Spring Boot? I want all beans created in the test to be destroyed after its execution, since I am creating the same beans in multiple tests. I don't want to have one configuration class for all tests, but configuration class per test, as shown bellow.
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = MyTest.ContextConfiguration.class)
public class MyTest{
...
public static class ContextConfiguration {
// beans defined here...
}
}
Putting #DirtiesContext(classMode = BEFORE_CLASS) doesn't work with Junit5.
You have claimed twice that #DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) does not work; however, the following shows that it works as documented.
import javax.annotation.PreDestroy;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#ContextConfiguration
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
class MyTest {
#Test
void test1(TestInfo testInfo) {
System.err.println(testInfo.getDisplayName());
}
#Test
void test2(TestInfo testInfo) {
System.err.println(testInfo.getDisplayName());
}
#Configuration
static class Config {
#Bean
MyComponent myComponent() {
return new MyComponent();
}
}
}
class MyComponent {
#PreDestroy
void destroy() {
System.err.println("Destroying " + this);
}
}
Executing the above test class results in output to STDERR similar to the following.
test1(TestInfo)
Destroying MyComponent#dc9876b
test2(TestInfo)
Destroying MyComponent#30b6ffe0
Thus, #DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) is indeed how you "clear the application context after each test execution".
Putting #DirtiesContext(classMode = BEFORE_CLASS) doesn't work with Junit5.
Based on my testing, classMode = BEFORE_CLASS works with TestNG, JUnit 4, and JUnit Jupiter (a.k.a., JUnit 5).
So, if that does not work in your test class, that would be a bug which you should report to the Spring Team.
Do you have any example where you can demonstrate that not working?
FYI: using classMode = BEFORE_CLASS would only ever make sense if the context had already been created within the currently executing test suite. Otherwise, you are instructing Spring to close and remove an ApplicationContext from the cache that does not exist... just before Spring actually creates it.
Regards,
Sam (author of the Spring TestContext Framework)
According to the docs, try #DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)

#WebMvcTest mapperscan conflict

My project used spring-boot (1.4.0.release) and mybatis-spring-boot-starter. When I try to have some test code for controller, I always get a exception
Caused by: java.lang.IllegalArgumentException: Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.mybatis.spring.support.SqlSessionDaoSupport.checkDaoConfig(SqlSessionDaoSupport.java:75)
at org.mybatis.spring.mapper.MapperFactoryBean.checkDaoConfig(MapperFactoryBean.java:74)
at org.springframework.dao.support.DaoSupport.afterPropertiesSet(DaoSupport.java:44)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
... 42 more`
But when I comment #MapperScan("com.toby.mapper"), it runs very well.
Here is my example class:
#MapperScan("com.toby.mapper")
#EnableTransactionManagement
#EnableConfigurationProperties(AppConfig.class)
#SpringBootApplication(scanBasePackages = "com.toby.configuration,com.toby.web.controller,com.toby.service,com.toby.dao")
public class Example {
public static void main(String[] args) throws Exception {
//new SpringApplicationBuilder().sources(Example.class).run(args);
SpringApplication application=new SpringApplication(Example.class);
application.addInitializers(new PropertyPasswordDecodingContextInitializer());
application.run(args);
}
}
Here is my test code:
package com.toby.web.controller;
import com.toby.common.config.AppConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
/**
* Created by Toby on 2016/8/10.
*/
#RunWith(SpringRunner.class)
#WebMvcTest(value = MyRestController.class)
public class MyRestControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private AppConfig appConfig;
#Test
public void testHome() throws Exception {
/*this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk()).andExpect(content().string("Honda Civic"));*/
}
}
I guess you've updated the description or I didn't read it properly the first time. #MapperScan is a mybatis specific annotation that triggers something but is missing some guard of some sort.
We had the same problem in boot actually. Let's say you put #EnableCaching on your main app. Because slicing disables all auto-configurations but a list of specific ones, the cache auto-configuration would not kick in and you'll get an exception because the CacheManager isn't found. To fix that issue, we've started to create some annotation to easily enable those. If you look at WebMbcTest you'll see it's annotated with AutoConfigureCache that's going to provide a dummy no-op cache manager unless specified otherwise.
Your problem is that the mybatis support is a third party integration and there isn't any support for that. Some solutions:
Change #WebMbvcTest to provide the class of another configuration class, effectivly disabling the use of your main spring boot app. Of course that class shouldn't define the #MapperScan annotation
Move the MapperScan (and anything that's not required with slicing) to another Configuration class. It could be a class in the same package as your app. Slicing won't scan those by default so you'll be fine. It's by far the easiest
Create an issue in the mybatis support so that they improve the auto-configuration to back-off (prevent this exception). I am not sure that's possible actually
Long story short, since #MapperScan is a way to tell mybatis to scan your entities, maybe you shouldn't add it on your main boot app if you use slicing. Because your #WebMbcTest doesn't want to use that obviously.

Multiple ApplicationRunners on classpath, how to make SpringApplication.run() only run one

Context: I have a project with some utilities to do things like data fixing. Each utility is a Java application, i.e. class with main() method. I want to define them as Spring Boot applications so I can use the ApplicationRunner and ApplicationArguments facility. The Spring configuration is defined via annotations in a shared configuration class. I've put a minimal example of this setup below.
Expectation: if I call SpringApplication.run(SomeClass.class, args) where SomeClass is an ApplicationRunner, it runs the run() on that class and not on any other classes that may be in the app context.
What actually happens: it calls all ApplicationRunners that it has in the context.
Why? I understood SpringApplication.run(Class, String[]) to mean, "run this class" whereas it appears to mean "load an app context from this class and run anything you can find in it". How should I fix it to run only 1 class? I don't mind if my other application class isn't in the app context, because all the configuration I need is in the shared config class. But I don't want to have to edit code (e.g. add or remove annotations) according to which class I need to run.
Minimal example:
A Spring config class (shared):
package com.stackoverflow.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ExampleSpringConfig {
/** Some bean - just here to check that beans from this config are injected */
#Bean public FooService fooService () {
return new FooService();
}
}
Two application classes
package com.stackoverflow.example;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
#SpringBootApplication
public class SomethingJob implements ApplicationRunner {
#Resource private FooService fooService;
public void run(ApplicationArguments args) throws Exception {
System.out.println("Doing something"); // do things with FooService here
}
public static void main(String[] args) {
SpringApplication.run(SomethingJob.class, args);
}
}
and another that is identical except that it prints "Doing something else".
Output:
[Spring Boot startup logs...]
Doing something else
Doing something
[Spring Boot shutdown logs...]
Firstly, only one class should be annotated with #SpringBootApplication. As you've noticed in your answer, this defines the external "main" entry point. I would recommend this is a different class to your ApplicationRunner classes for clarity and conceptual separation.
To only have some but not all runners run, I've done this by parsing the arguments, and quickly exiting from the runner which should not be called. e.g.
package com.stackoverflow.example;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
#Component
public class SomethingJob implements ApplicationRunner {
#Resource private FooService fooService;
public void run(ApplicationArguments args) throws Exception {
if (!args.containsOption("something")) return
System.out.println("Doing something"); // do things with FooService here
}
}
That way you can do java -jar myjar.jar --something or java -jar myjar.jar --something-else depending which one you want to be run.
I found a workaround while experimenting with my minimal example.
#SpringBootApplication is just an alias for #ComponentScan, #EnableAutoConfiguration and #Configuration. By applying them separately, I discovered that it's the #Configuration annotation that causes this behaviour. If I only apply the other 2, I don't get the issue.
I guess this is because #Configuration means "I'm a configuration class, and any beans I define should be pulled into the context during component scan" and although this class doesn't define an ApplicationRunner, it is one, which has the same effect. Therefore if you have 2 such beans on the classpath, they both get pulled into the app context.
Without #Configuration, the bean you want to run still gets registered since it's referenced by the call to run(), but other ApplicationRunners on the classpath don't.
This fixes my immediate problem by making sure I only have one ApplicationRunner in my app context. But it doesn't answer the wider question, "If I do have several ApplicationRunners, how do I tell Spring Boot which one to run?" So I'd still appreciate any more complete answer or suggestions for a different approach.

Resources