Skip to content

Commit 2dd5875

Browse files
committed
Support @ControllerAdvice in StandaloneMockMvcBuilder
Issue: SPR-12751
1 parent cc33d3f commit 2dd5875

File tree

5 files changed

+94
-11
lines changed

5 files changed

+94
-11
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
import org.springframework.beans.BeansException;
2828
import org.springframework.beans.factory.BeanInitializationException;
2929
import org.springframework.beans.factory.InitializingBean;
30+
import org.springframework.context.ApplicationContextAware;
3031
import org.springframework.format.support.DefaultFormattingConversionService;
3132
import org.springframework.format.support.FormattingConversionService;
3233
import org.springframework.http.converter.HttpMessageConverter;
@@ -86,6 +87,8 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
8687

8788
private final Object[] controllers;
8889

90+
private List<Object> controllerAdvice;
91+
8992
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
9093

9194
private List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<HandlerMethodArgumentResolver>();
@@ -100,7 +103,7 @@ public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder<StandaloneM
100103

101104
private FormattingConversionService conversionService = null;
102105

103-
private List<HandlerExceptionResolver> handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>();
106+
private List<HandlerExceptionResolver> handlerExceptionResolvers;
104107

105108
private Long asyncRequestTimeout;
106109

@@ -128,6 +131,19 @@ protected StandaloneMockMvcBuilder(Object... controllers) {
128131
this.controllers = controllers;
129132
}
130133

134+
/**
135+
* Register {@link org.springframework.web.bind.annotation.ControllerAdvice
136+
* ControllerAdvice} instances to be used with this MockMvc instance.
137+
* <p>Normally {@code @ControllerAdvice} are auto-detected. However since the
138+
* standalone setup does not load Spring configuration they need to be
139+
* registered explicitly instead.
140+
* @since 4.2
141+
*/
142+
public StandaloneMockMvcBuilder setControllerAdvice(Object... controllerAdvice) {
143+
this.controllerAdvice = Arrays.asList(controllerAdvice);
144+
return this;
145+
}
146+
131147
/**
132148
* Set the message converters to use in argument resolvers and in return value
133149
* handlers, which support reading and/or writing to the body of the request
@@ -317,6 +333,9 @@ protected WebApplicationContext initWebAppContext() {
317333

318334
private void registerMvcSingletons(StubWebApplicationContext wac) {
319335
StandaloneConfiguration config = new StandaloneConfiguration();
336+
config.setApplicationContext(wac);
337+
338+
wac.addBeans(this.controllerAdvice);
320339

321340
StaticRequestMappingHandlerMapping hm = config.getHandlerMapping();
322341
hm.setServletContext(wac.getServletContext());
@@ -427,7 +446,23 @@ public Validator mvcValidator() {
427446

428447
@Override
429448
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
430-
exceptionResolvers.addAll(StandaloneMockMvcBuilder.this.handlerExceptionResolvers);
449+
if (handlerExceptionResolvers == null) {
450+
return;
451+
}
452+
for (HandlerExceptionResolver resolver : handlerExceptionResolvers) {
453+
if (resolver instanceof ApplicationContextAware) {
454+
((ApplicationContextAware) resolver).setApplicationContext(getApplicationContext());
455+
}
456+
if (resolver instanceof InitializingBean) {
457+
try {
458+
((InitializingBean) resolver).afterPropertiesSet();
459+
}
460+
catch (Exception ex) {
461+
throw new IllegalStateException("Failure from afterPropertiesSet", ex);
462+
}
463+
}
464+
exceptionResolvers.add(resolver);
465+
}
431466
}
432467
}
433468

spring-test/src/main/java/org/springframework/test/web/servlet/setup/StubWebApplicationContext.java

+3
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ public void addBean(String name, Object bean) {
137137
}
138138

139139
public void addBeans(List<?> beans) {
140+
if (beans == null) {
141+
return;
142+
}
140143
for (Object bean : beans) {
141144
String name = bean.getClass().getName() + "#" + ObjectUtils.getIdentityHexString(bean);
142145
this.beanFactory.addBean(name, bean);

spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/ExceptionHandlerTests.java

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,25 +16,27 @@
1616

1717
package org.springframework.test.web.servlet.samples.standalone;
1818

19+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
20+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
21+
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
22+
1923
import org.junit.Test;
2024

2125
import org.springframework.stereotype.Controller;
26+
import org.springframework.web.bind.annotation.ControllerAdvice;
2227
import org.springframework.web.bind.annotation.ExceptionHandler;
2328
import org.springframework.web.bind.annotation.PathVariable;
2429
import org.springframework.web.bind.annotation.RequestMapping;
2530
import org.springframework.web.bind.annotation.RequestMethod;
2631

27-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
28-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
29-
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
30-
3132
/**
3233
* Exception handling via {@code @ExceptionHandler} method.
3334
*
3435
* @author Rossen Stoyanchev
3536
*/
3637
public class ExceptionHandlerTests {
3738

39+
3840
@Test
3941
public void testExceptionHandlerMethod() throws Exception {
4042
standaloneSetup(new PersonController()).build()
@@ -43,14 +45,25 @@ public void testExceptionHandlerMethod() throws Exception {
4345
.andExpect(forwardedUrl("errorView"));
4446
}
4547

48+
@Test
49+
public void testGlobalExceptionHandlerMethod() throws Exception {
50+
standaloneSetup(new PersonController()).setControllerAdvice(new GlobalExceptionHandler()).build()
51+
.perform(get("/person/Bonnie"))
52+
.andExpect(status().isOk())
53+
.andExpect(forwardedUrl("globalErrorView"));
54+
}
55+
4656

4757
@Controller
4858
private static class PersonController {
4959

5060
@RequestMapping(value="/person/{name}", method=RequestMethod.GET)
5161
public String show(@PathVariable String name) {
5262
if (name.equals("Clyde")) {
53-
throw new IllegalArgumentException("Black listed");
63+
throw new IllegalArgumentException("simulated exception");
64+
}
65+
else if (name.equals("Bonnie")) {
66+
throw new IllegalStateException("simulated exception");
5467
}
5568
return "person/show";
5669
}
@@ -60,4 +73,15 @@ public String handleException(IllegalArgumentException exception) {
6073
return "errorView";
6174
}
6275
}
76+
77+
@ControllerAdvice
78+
private static class GlobalExceptionHandler {
79+
80+
@ExceptionHandler
81+
public String handleException(IllegalStateException exception) {
82+
return "globalErrorView";
83+
}
84+
85+
}
86+
6387
}

spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ModelAssertionTests.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,8 @@
2626
import org.springframework.test.web.servlet.MockMvc;
2727
import org.springframework.ui.Model;
2828
import org.springframework.validation.BindingResult;
29+
import org.springframework.web.bind.annotation.ControllerAdvice;
30+
import org.springframework.web.bind.annotation.ModelAttribute;
2931
import org.springframework.web.bind.annotation.RequestMapping;
3032
import org.springframework.web.bind.annotation.RequestMethod;
3133

@@ -43,6 +45,7 @@ public class ModelAssertionTests {
4345

4446
private MockMvc mockMvc;
4547

48+
4649
@Before
4750
public void setup() {
4851

@@ -51,6 +54,7 @@ public void setup() {
5154
this.mockMvc = standaloneSetup(controller)
5255
.defaultRequest(get("/"))
5356
.alwaysExpect(status().isOk())
57+
.setControllerAdvice(new ModelAttributeAdvice())
5458
.build();
5559
}
5660

@@ -60,7 +64,8 @@ public void testAttributeEqualTo() throws Exception {
6064
.andExpect(model().attribute("integer", 3))
6165
.andExpect(model().attribute("string", "a string value"))
6266
.andExpect(model().attribute("integer", equalTo(3))) // Hamcrest...
63-
.andExpect(model().attribute("string", equalTo("a string value")));
67+
.andExpect(model().attribute("string", equalTo("a string value")))
68+
.andExpect(model().attribute("globalAttrName", equalTo("Global Attribute Value")));
6469
}
6570

6671
@Test
@@ -113,4 +118,13 @@ public String create(@Valid Person person, BindingResult result, Model model) {
113118
}
114119
}
115120

121+
@ControllerAdvice
122+
private static class ModelAttributeAdvice {
123+
124+
@ModelAttribute("globalAttrName")
125+
public String getAttribute() {
126+
return "Global Attribute Value";
127+
}
128+
}
129+
116130
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

+7
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ public void setApplicationContext(ApplicationContext applicationContext) {
207207
this.applicationContext = applicationContext;
208208
}
209209

210+
public ApplicationContext getApplicationContext() {
211+
return this.applicationContext;
212+
}
213+
210214
/**
211215
* Set the {@link javax.servlet.ServletContext}, e.g. for resource handling,
212216
* looking up file extensions, etc.
@@ -216,6 +220,9 @@ public void setServletContext(ServletContext servletContext) {
216220
this.servletContext = servletContext;
217221
}
218222

223+
public ServletContext getServletContext() {
224+
return this.servletContext;
225+
}
219226

220227
/**
221228
* Return a {@link RequestMappingHandlerMapping} ordered at 0 for mapping

0 commit comments

Comments
 (0)