22

From Spring 3.1, we can use JavaConfig more easily thanks to @Enable* annotations.

So I made a WebConfig to set WebMvc configuration, and tried to test it. But if I extends WebMvcConfigurerAdapter or WebMvcConfigurationSupport with WebConfig the unit test fails because of lack of ServletContext. The code and messages look like below.

WebConfig.java

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {}

Test.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=WebConfig.class)
public class TestFail {
    @Test
    public void test() {}
}

Message

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:157)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
    at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
...
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:253)
    at com.zum.news.comments.web.WebConfig$$EnhancerByCGLIB$$8bbfcca1.CGLIB$defaultServletHandlerMapping$10(<generated>)
    at com.zum.news.comments.web.WebConfig$$EnhancerByCGLIB$$8bbfcca1$$FastClassByCGLIB$$19b86ad0.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:215)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:280)
    at com.zum.news.comments.web.WebConfig$$EnhancerByCGLIB$$8bbfcca1.defaultServletHandlerMapping(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:149)
    ... 41 more

How to unit test the WebConfig properly?

Edit

As Garcia said, this bug is fixed in Spring 3.2.0.RC1.

Just add @WebAppConfiguration annotation in the test class.

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes=WebConfig.class)
public class TestFail {
    @Test
    public void test() {}
}
Sanghyun Lee
  • 21,644
  • 19
  • 100
  • 126
  • 1
    I don't understand how this question has so few votes. I suppose there are people out there trying to do integration tests with Spring 3.1 niceties... – Guido Oct 21 '12 at 20:18
  • 1
    The question was precisely what I was looking for and the answer is included in the "Edit" section – Markus Pscheidt Dec 15 '15 at 16:00

4 Answers4

12

As Guido mentioned previously, this has been solved as of 3.2. The following are the details of how to take advantage of the new test improvements. To ensure that a servlet context is loaded for your test, you need to annotate your test with @WebAppConfiguration and define AnnotationConfigWebContextLoader as your context loader, as below:

@RunWith(SpringJUnit4ClassRunner.class)    
@WebAppConfiguration
@ContextConfiguration(
    classes = MyWebConfig.class, 
    loader = AnnotationConfigWebContextLoader.class)
public class MyTest {
    //...
}
Steve
  • 9,270
  • 5
  • 47
  • 61
  • Could you explain how who is application spring context is loaded in these test cases. I appreciate the MyWebConfig class replaces the servlet-context.xml file for initialising the web app but if our Controller is dependent on other beans to be injected how is this done. In other examples a 'location=classparth=context.xml' is passed to the ContextConfiguration - is this still possible in the example above? – emeraldjava Oct 20 '14 at 13:38
  • 1
    In the `@Configuration` class. That defines what classpaths should be scanned for beans annotated as `@Component`, `@Controller`, `@Service`, etc. It can also use the `Import` annotation to import an XML context. You can create `@Configuration` classes specifically for your tests if you like. – Steve Oct 20 '14 at 14:03
  • Thanks. In our real web app, i have a ContextLoaderListener in the web.xml file which loads a contextConfigLocation='/WEB-INF/applicationContext.xml'. For my test case, i think you are suggesting I add the Import statement to my WebAppConfig class but wouldn't this mean the context is loaded twice if the real webapp. I might have expected the test class code to do something to load the applicationContext.xml rather than the read config class. – emeraldjava Oct 20 '14 at 14:25
  • You can remove the loader from the web.xml. Or even delete web.xml completely if you put a bit of time into replacing it with `@configuration` classes. Alternatively, create a config in your test classes, which is not used by your running application. – Steve Oct 20 '14 at 14:39
7

If @EnableWebMvc annotation require ServletContext then I suggest to split your config to beans definitions which will be used in unit tests and other configuration which used by application and framework. In this case application will import both configs and unit tests will import only one.

BeansConfig.java:

@Configuration
public class BeansConfig {
    @Bean
    MyBean myBean() {
        return new MyBean()
    }
}

WebConfig.java:

@Configuration
@EnableWebMvc
@Import(BeansConfig.class)
public class WebConfig extends WebMvcConfigurationSupport {}

TestFail.java:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=BeansConfig.class)
public class TestFail {
    @Test
    public void test() {}
}
Slava Semushin
  • 14,904
  • 7
  • 53
  • 69
  • Actually, that is what I'm doing now. I wonder why the unit test fails. It was ok when configured with XML in Spring 3.0. – Sanghyun Lee Jul 04 '12 at 00:10
6

There is a bug in Spring 3.1, you can find the answer in these two issues:

Please let us know if you find a workaround for Spring 3.1, if not we must wait until 3.2 is out there. I have to say that I've just tried it with Spring 3.2.0.M2 and it is still not working for me.

Guido
  • 46,642
  • 28
  • 120
  • 174
  • 1
    3.2.0.RC1 is out there and the bug is fixed. It is supposed to work on 3.2.0.M2 as well, as long as you annotate your test classes with @WebAppConfiguration. – Guido Nov 11 '12 at 22:08
2

Another recommendation that I have would be to use spring-test-mvc, which internally creates a mock servlet context for the Controller tests to work.

If you want to continue with your approach, you may then have to create your own Spring Context loader that additionally initializes a Mock servlet context - along these lines: http://tedyoung.me/2011/02/14/spring-mvc-integration-testing-controllers/, From Spring-test-mvc source

Biju Kunjummen
  • 49,138
  • 14
  • 112
  • 125