Hot questions for Testing module of the Spring

Top 10 Java Open Source / Spring / Testing module of the Spring

Question:

I'd like to write some tests that check the XML Spring configuration of a deployed WAR. Unfortunately some beans require that some environment variables or system properties are set. How can I set an environment variable before the spring beans are initialized when using the convenient test style with @ContextConfiguration?

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:whereever/context.xml")
public class TestWarSpringContext { ... }

If I configure the application context with annotations, I don't see a hook where I can do something before the spring context is initialized.


Answer:

You can initialize the System property in a static initializer:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:whereever/context.xml")
public class TestWarSpringContext {

    static {
        System.setProperty("myproperty", "foo");
    }

}

The static initializer code will be executed before the spring application context is initialized.

Question:

We've a bunch of JUnit test cases (Integration tests) and they are logically grouped into different test classes.

We are able to load Spring application context once per test class and re-use it for all test cases in a JUnit test class as mentioned in http://static.springsource.org/spring/docs/current/spring-framework-reference/html/testing.html

However, we were just wondering if there is a way to load Spring application context only once for a bunch of JUnit test classes.

FWIW, we use Spring 3.0.5, JUnit 4.5 and use Maven to build the project.


Answer:

Yes, this is perfectly possible. All you have to do is to use the same locations attribute in your test classes:

@ContextConfiguration(locations = "classpath:test-context.xml")

Spring caches application contexts by locations attribute so if the same locations appears for the second time, Spring uses the same context rather than creating a new one.

I wrote an article about this feature: Speeding up Spring integration tests. Also it is described in details in Spring documentation: 9.3.2.1 Context management and caching.

This has an interesting implication. Because Spring does not know when JUnit is done, it caches all context forever and closes them using JVM shutdown hook. This behavior (especially when you have a lot of test classes with different locations) might lead to excessive memory usage, memory leaks, etc. Another advantage of caching context.

Question:

First of all, I've found a lot of threads on StackOverflow about this, but none of them really helped me, so sorry to ask possibly duplicate question.

I'm running JUnit tests using spring-test, my code looks like this

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = {})
public class StudentSystemTest {

    @Autowired
    private StudentSystem studentSystem;

    @Before
    public void initTest() {
    // set up the database, create basic structure for testing
    }

    @Test
    public void test1() {
    }    
    ...  
}

My problem is that I want my tests to NOT influence other tests. So I'd like to create something like rollback for each test. I've searched a lot for this, but I've found nothing so far. I'm using Hibernate and MySql for this


Answer:

Just add @Transactional annotation on top of your test:

@RunWith(SpringJUnit4ClassRunner.class)  
@ContextConfiguration(locations = {"testContext.xml"})
@Transactional
public class StudentSystemTest {

By default Spring will start a new transaction surrounding your test method and @Before/@After callbacks, rolling back at the end. It works by default, it's enough to have some transaction manager in the context.

From: 10.3.5.4 Transaction management (bold mine):

In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener. Note that TransactionalTestExecutionListener is configured by default, even if you do not explicitly declare @TestExecutionListeners on your test class. To enable support for transactions, however, you must provide a PlatformTransactionManager bean in the application context loaded by @ContextConfiguration semantics. In addition, you must declare @Transactional either at the class or method level for your tests.

Question:

The following code is invalid due to duplicate @RunWith annotation:

@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(Parameterized.class)
@SpringApplicationConfiguration(classes = {ApplicationConfigTest.class})
public class ServiceTest {
}

But how can I use these two annotations in conjunction?


Answer:

You can use SpringClassRule and SpringMethodRule - supplied with Spring

import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;

@RunWith(Parameterized.class)
@ContextConfiguration(...)
public class MyTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    ...

Question:

I have the following test class:

@ActiveProfiles({ "DataTC", "test" })
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {BaseTestConfiguration.class, DataTestConfiguration.class, JpaConfiguration.class, PropertyPlaceholderConfiguration.class })
public class RegularDayToTimeSlotsTest {
...

The issue seems to come from the BaseTestConfiguration class:

@Configuration
@ComponentScan(basePackages = { "com.bignibou" }, excludeFilters = { @Filter(type = FilterType.CUSTOM, value = RooRegexFilter.class),
        @Filter(type = FilterType.ANNOTATION, value = Controller.class), @Filter(type = FilterType.ANNOTATION, value = ControllerAdvice.class) })
public class BaseTestConfiguration {

}

I systematically get this exception:

Caused by: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.<init>(DefaultServletHandlerConfigurer.java:54)
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping(WebMvcConfigurationSupport.java:329)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerByCGLIB$$bb4ceb44.CGLIB$defaultServletHandlerMapping$22(<generated>)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerByCGLIB$$bb4ceb44$$FastClassByCGLIB$$368bb5c1.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:326)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerByCGLIB$$bb4ceb44.defaultServletHandlerMapping(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
    ... 43 more

I am not sure how to get around this issue. Somehow Spring is looking for a ServletContext when I run the test and I get the above exception...


Answer:

One of your @Configuration classes is obviously annotated with @EnableWebMvc. That's how DelegatingWebMvcConfiguration ends up in your stack trace, since it is imported by @EnableWebMvc.

So although you think you don't need a WebApplicationContext (and hence a ServletContext), you in fact do need it simply because you are loading an application context with @EnableWebMvc.

You have two options:

  • Compose the configuration classes for your integration test so that you are not including the web-related configuration (i.e., the @Configuration class(es) annotated with @EnableWebMvc).
  • Annotate your test class with @WebAppConfiguration as suggested in other comments above.

Regards,

Sam (author of the Spring TestContext Framework)

Question:

I have following test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/services-test-config.xml"})
public class MySericeTest {

  @Autowired
  MyService service;
...

}

Is it possible to access services-test-config.xml programmatically in one of such methods? Like:

ApplicationContext ctx = somehowGetContext();

Answer:

This works fine too:

@Autowired
ApplicationContext context;

Question:

I have a simple annotated controller similar to this one:

@Controller
public class MyController {
  @RequestMapping("/{id}.html")
  public String doSomething(@PathVariable String id, Model model) {
    // do something
    return "view";
  }
}

and I want to test it with an unit test like this:

public class MyControllerTest {
  @Test
  public void test() {
    MockHttpServletRequest request = new MockHttpServletRequest();
    request.setRequestURI("/test.html");
    new AnnotationMethodHandlerAdapter()
      .handle(request, new MockHttpServletResponse(), new MyController());
    // assert something
  }
}

The problem is that AnnotationMethodHandlerAdapter.handler() method throws an exception:

java.lang.IllegalStateException: Could not find @PathVariable [id] in @RequestMapping
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.resolvePathVariable(AnnotationMethodHandlerAdapter.java:642)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.java:514)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:262)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:146)

Answer:

I'd call what you're after an integration test based on the terminology in the Spring reference manual. How about doing something like:

import static org.springframework.test.web.ModelAndViewAssert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({/* include live config here
    e.g. "file:web/WEB-INF/application-context.xml",
    "file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {

    @Inject
    private ApplicationContext applicationContext;

    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private HandlerAdapter handlerAdapter;
    private MyController controller;

    @Before
    public void setUp() {
       request = new MockHttpServletRequest();
       response = new MockHttpServletResponse();
       handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
       // I could get the controller from the context here
       controller = new MyController();
    }

    @Test
    public void testDoSomething() throws Exception {
       request.setRequestURI("/test.html");
       final ModelAndView mav = handlerAdapter.handle(request, response, 
           controller);
       assertViewName(mav, "view");
       // assert something
    }
}

For more information I've written a blog entry about integration testing Spring MVC annotations.

Question:

My Spring-Boot-Mvc-Web application has the following database configuration in application.properties file:

spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

this is the only config I made. No any other configurations made by me anywhere. Nevertheless the Spring and subsystems are automatically recreate database on each web application run. Database is recreated namely on system run while it contains data after application ends.

I was not understanding this defaults and was expecting this is suitable for tests.

But when I started to run tests I found that database is recreated only once. Since tests are executed at no predefined order, this is senseless at all.

So, the question is: how to make any sense? I.e. how to make database recreate before each test as it happens at application first start?

My test class header is follows:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {

As you see, I tried @DirtiesContext at class level and it didn't help.

UPDATE

I have a bean

@Service
public class DatabaseService implements InitializingBean {

which has a method

@Override
    @Transactional()
    public void afterPropertiesSet() throws Exception {
        log.info("Bootstrapping data...");
        User user = createRootUser();
        if(populateDemo) {
            populateDemos();
        }
        log.info("...Bootstrapping completed");
    }

Now I made it's populateDemos() method to clear all data from database. Unfortunately, it does not called before each test despite @DirtiesContext. Why?


Answer:

Actually, I think you want this:

@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)

http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html

@DirtiesContext may be used as a class-level and method-level annotation within the same class. In such scenarios, the ApplicationContext will be marked as dirty after any such annotated method as well as after the entire class. If the DirtiesContext.ClassMode is set to AFTER_EACH_TEST_METHOD, the context will be marked dirty after each test method in the class.

Question:

On my spring boot application I want to override just one of my @Configuration classes with a test configuration (in particular my @EnableAuthorizationServer @Configuration class), on all of my tests.

So far after an overview of spring boot testing features and spring integration testing features no straightforward solution has surfaced:

  • @TestConfiguration: It's for extending, not overriding;
  • @ContextConfiguration(classes=…​) and @SpringApplicationConfiguration(classes =…​) lets me override the whole config, not just the one class;
  • An inner @Configuration class inside a @Test is suggested to override the default configuration, but no example is provided;

Any suggestions?


Answer:

Inner test configuration

Example of an inner @Configuration for your test:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SomeTest {

    @Configuration
    static class ContextConfiguration {
        @Bean
        @Primary //may omit this if this is the only SomeBean defined/visible
        public SomeBean someBean () {
            return new SomeBean();
        }
    }

    @Autowired
    private SomeBean someBean;

    @Test
    public void testMethod() {
        // test
    }
}

Reusable test configuration

If you wish to reuse the Test Configuration for multiple tests, you may define a standalone Configuration class with a Spring Profile @Profile("test"). Then, have your test class activate the profile with @ActiveProfiles("test"). See complete code:

@RunWith(SpringRunner.class)
@SpringBootTests
@ActiveProfiles("test")
public class SomeTest {

    @Autowired
    private SomeBean someBean;

    @Test
    public void testMethod() {
        // test
    }
}

@Configuration
@Profile("test")
public class TestConfiguration {
    @Bean
    @Primary //may omit this if this is the only SomeBean defined/visible
    public SomeBean someBean() {
        return new SomeBean();
    }
}

@Primary

The @Primary annotation on the bean definition is to ensure that this one will have priority if more than one are found.

Question:

How do I configure my Spring Boot application so that when I run unit tests it will use in-memory database such as H2/HSQL but when I run Spring Boot application it will use production database [Postgre/MySQL] ?


Answer:

Spring profiles can be used for this. This would be a specific way:

Have environment specific properties files:

application.properties:

spring.profiles.active: dev

application-dev.properties

spring.jpa.database: MYSQL
spring.jpa.hibernate.ddl-auto: update

spring.datasource.url: jdbc:mysql://localhost:3306/dbname
spring.datasource.username: username
spring.datasource.password: password

application-test.properties

spring.jpa.database: HSQL

Have both MySQL and H2 drivers in pom.xml, like this:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>test</scope>
</dependency>

Last but not the least, annotate Test classes with @ActiveProfiles("test").

Question:

I have a very simple test case that is using Mockito and Spring Test framework. When I do

when(pcUserService.read("1")).thenReturn(pcUser);

I get this exception.

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.

    at com.project.cleaner.controller.test.PcUserControllerTest.shouldGetPcUser(PcUserControllerTest.java:93)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)

I have tried with different methods but keep on getting this error message. I am using Spring 3.1.0.RELEASE with Mockito. Please share and guide me in the right direction.


Answer:

You need to create a MOCK of pcUserService first, and then use that mock.

PcUserService mock = org.mockito.Mockito.mock(PcUserService.class);
when(mock.read("1")).thenReturn(pcUser);