Spring WebMvc Unit Test fails. “Caused by: java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling”

Sometimes when you are using Spring java config and trying to run a unit test, you’ll find that you cannot run the tests unless you comment out @EnableWebMVC, which can cost you some time (or at least it did me).  The runner complains that “A ServletContext is required to configure default servlet handling” while you think to yourself “why do I care?”

The solution?  A simple combination of Spring profiles, and a custom test config class.

First, your webapp intializer which probably sets up your context.  Alternatively this may be in your web.xml.  In either case the important thing is that you are setting an active profile on the servlet dispatcher.

Things to note are the injection of the application config into the root context, the web config into the dispatcher context, and the active profile setting on the dispatcher.

@Profile("container")
public class WebAppInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext container) {

        //Load Annotation Based Configs
    	AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        container.addListener(new ContextLoaderListener(rootContext));
        rootContext.register(ApplicationConfiguration.class); 

        ... root config stuff ...

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(MVCConfiguration.class); 
        dispatcherContext.scan("com.foo");

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher =
                container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
        dispatcher.setInitParameter("spring.profiles.active", "container"); 

    }
 }

Next, your MVCConfiguration.java. Things to note are just the profile to run in, and the fact that this contains your @EnableWebMvc.

@Configuration
@Profile("container") 
@EnableWebMvc
public class MVCConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/*.html").addResourceLocations("/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/img/**").addResourceLocations("/img/");
    }

    ... The rest of your mvc config related stuff...
}

Then, your ApplicationConfiguration.java. Nothing in particular to note here except that it is your primary app config, no web mvc related stuff, and no profile specified.

@Configuration
@ImportResource( { "classpath:/spring/security.xml" } )
@PropertySource(value = { "classpath:some.properties"})
public class ApplicationConfiguration  {

    @Autowired
    private Environment environment;

    ... Various beans for your application that aren't web specific and should be made available to tests as well...
    public @Bean
    MongoDbFactory mongoDbFactory() throws Exception {
        UserCredentials userCredentials = new UserCredentials(environment.getProperty("mongodb.username"), environment.getProperty("mongodb.password"));
        return new SimpleMongoDbFactory(mongo().getObject(), environment.getProperty("mongodb.database"), userCredentials);
    }
}

Next, your TestConfig.java, which explicitly excludes the class that contains @EnableWebMvc, and runs it its own profile. Things to note are the customization of the @ComponentScan which has specific exclusions for the MvcConfiguration and the WebAppInitializer as well as the @Import of the ApplicationConfiguration and the setting of the active profile.

@Configuration
@ComponentScan(basePackages="com.foo",
                
                    excludeFilters = { 
                        @ComponentScan.Filter(
                              type = FilterType.ASSIGNABLE_TYPE, 
                              value = { MVCConfiguration.class, WebAppInitializer.class }
                        ) 
                    }
                 
)
@Import(ApplicationConfiguration.class) 
@ActiveProfiles("integration-test") 
public class TestConfig {

}

Finally, the unit test which previously was blowing up because it wanted a servlet context, should now work by simply swapping out the config it uses for bootstrapping the context.

@ContextConfiguration(classes=TestConfig.class) 
@RunWith(SpringJUnit4ClassRunner.class)
public class AnyTestRequireSpring {

    @Test
    public void testSomeBeanBehavior() {
    }
}

Hope this helps….