Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's required to make mockMVC test a filter's init routine?

Tags:

I have implemented the following CORS filter, which works when the code is executed on the server:

/*  *    Copyright 2013 BrandsEye (http://www.brandseye.com)  *  *    Licensed under the Apache License, Version 2.0 (the "License");  *    you may not use this file except in compliance with the License.  *    You may obtain a copy of the License at  *  *        http://www.apache.org/licenses/LICENSE-2.0  *  *    Unless required by applicable law or agreed to in writing, software  *    distributed under the License is distributed on an "AS IS" BASIS,  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *    See the License for the specific language governing permissions and  *    limitations under the License.  */  package org.energyos.espi.datacustodian.web.filter;  import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Pattern;  import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;  import org.springframework.stereotype.Component;  /**  * Adds CORS headers to requests to enable cross-domain access.  */  @Component public class CORSFilter implements Filter {      private final Log logger = LogFactory.getLog(getClass());     private final Map<String, String> optionsHeaders = new LinkedHashMap<String, String>();      private Pattern allowOriginRegex;     private String allowOrigin;     private String exposeHeaders;      public void init(FilterConfig cfg) throws ServletException {         String regex = cfg.getInitParameter("allow.origin.regex");         if (regex != null) {             allowOriginRegex = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);         } else {             optionsHeaders.put("Access-Control-Allow-Origin", "*");         }          optionsHeaders.put("Access-Control-Allow-Headers", "Origin, Authorization, Accept, Content-Type");         optionsHeaders.put("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");         optionsHeaders.put("Access-Control-Max-Age", "1800");         for (Enumeration<String> i = cfg.getInitParameterNames(); i.hasMoreElements(); ) {             String name = i.nextElement();             if (name.startsWith("header:")) {                 optionsHeaders.put(name.substring(7), cfg.getInitParameter(name));             }         }          //maintained for backward compatibility on how to set allowOrigin if not         //using a regex         allowOrigin = optionsHeaders.get("Access-Control-Allow-Origin");         //since all methods now go through checkOrigin() to apply the Access-Control-Allow-Origin         //header, and that header should have a single value of the requesting Origin since         //Access-Control-Allow-Credentials is always true, we remove it from the options headers         optionsHeaders.remove("Access-Control-Allow-Origin");          exposeHeaders = cfg.getInitParameter("expose.headers");     }      public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)             throws IOException, ServletException {          if (logger.isDebugEnabled()) {                       logger.debug("CORSFilter processing: Checking for Cross Origin pre-flight OPTIONS message");         }          if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {             HttpServletRequest req = (HttpServletRequest)request;             HttpServletResponse resp = (HttpServletResponse)response;             if ("OPTIONS".equals(req.getMethod())) {                 allowOrigin = "*";                                          //%%%%% Test force of allowOrigin                 if (checkOrigin(req, resp)) {                     for (Map.Entry<String, String> e : optionsHeaders.entrySet()) {                         resp.addHeader(e.getKey(), e.getValue());                     }                      // We need to return here since we don't want the chain to further process                     // a preflight request since this can lead to unexpected processing of the preflighted                     // request or a 40x - Response Code                     return;                  }             } else if (checkOrigin(req, resp)) {                 if (exposeHeaders != null) {                     resp.addHeader("Access-Control-Expose-Headers", exposeHeaders);                 }             }         }         filterChain.doFilter(request, response);     }      private boolean checkOrigin(HttpServletRequest req, HttpServletResponse resp) {         String origin = req.getHeader("Origin");         if (origin == null) {             //no origin; per W3C specification, terminate further processing for both pre-flight and actual requests             return false;         }          boolean matches = false;         //check if using regex to match origin         if (allowOriginRegex != null) {             matches = allowOriginRegex.matcher(origin).matches();         } else if (allowOrigin != null) {             matches = allowOrigin.equals("*") || allowOrigin.equals(origin);         }          if (matches) {              // Activate next two lines and comment out third line if Credential Support is required //          resp.addHeader("Access-Control-Allow-Origin", origin); //          resp.addHeader("Access-Control-Allow-Credentials", "true");                      resp.addHeader("Access-Control-Allow-Origin", "*");             return true;         } else {             return false;         }     }      public void destroy() {     } } 

The following JUnit test uses mockMVC but fails, because the CORSFilter's "init" logic is not being executed (proven by breakpointing the JUnit test):

package org.energyos.espi.datacustodian.integration.web.filters;   import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;  import javax.servlet.FilterConfig;  import org.energyos.espi.datacustodian.web.filter.CORSFilter;  import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.web.context.WebApplicationContext;  import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;  @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration("/spring/test-context.xml") @Profile("test") public class CORSFilterTests {      private final Log logger = LogFactory.getLog(getClass());         @Autowired     private CORSFilter filter;      @Autowired     private WebApplicationContext wac;      private MockMvc mockMvc;      @Before     public void setup() {         this.mockMvc = webAppContextSetup(this.wac)                 .addFilters(filter).build();     }      @Test     public void optionsResponse_hasCorrectFilters() throws Exception {          RequestBuilder requestBuilder = MockMvcRequestBuilders.options("/DataCustodian/oauth/token")                 .header("Origin", "foobar")                 .header("Access-Control-Allow-Origin", "*");           MvcResult result =   mockMvc.perform(requestBuilder)                 .andExpect(header().string("Access-Control-Allow-Origin", is("*")))                 .andExpect(header().string("Access-Control-Allow-Methods", is("GET, POST, PUT, DELETE, OPTIONS")))                 .andExpect(header().string("Access-Control-Allow-Headers", is("origin, authorization, accept, content-type")))                 .andExpect(header().string("Access-Control-Max-Age", is("1800")))                                .andReturn();               }     } } 

I have reviewed the available material on the internet, which seems to imply the ".addfilter(filter). element of the mockMVC @Before section should be executing the CORSFilter init routine. However, that is clearly NOT happening.

Any suggestions or recommendations would be greatly appreciated, as I am really stuck understanding how to get the "init" routine tested using the mockMVC capability.

like image 460
Donald F. Coffin Avatar asked Dec 28 '13 20:12

Donald F. Coffin


People also ask

Is MockMvc a unit or integration test?

MockMvc is defined as a main entry point for server-side Spring MVC testing. Tests with MockMvc lie somewhere between between unit and integration tests.


2 Answers

The Spring MVC Test suite is not meant to test the container configuration, it is meant to test your MVC (@Controller and other mappings) configuration . Filter#init(ServletConfig) is a container managed method.

If you really need to test it, you can mock that too

@Before public void setup() {     filter.init(someMockFilterConfig); // using a mock that you construct with init params and all     this.mockMvc = webAppContextSetup(this.wac)             .addFilters(filter).build(); } 
like image 118
Sotirios Delimanolis Avatar answered Sep 22 '22 21:09

Sotirios Delimanolis


After lots of tests, here's what we adopted:

  • for testing a @RestController use MockMvc.
  • for testing a Filter or other infrastructure elements, use TestRestTemplate.

With MockMvc, addFilter(Filter) did not result in the execution of the filter at all. The solution with TestRestTemplate is more primitive, but all Filters configured in your application/libraries are executed. Example:

@RunWith(SpringRunner.class) @SpringBootTest(classes = MySpringBootApplication.class, webEnvironment=  SpringBootTest.WebEnvironment.RANDOM_PORT) public class MyRestControllerTest {      @LocalServerPort     private int port;      @Test     public void myTestCase() throws Exception {          HttpStatus expectedStatusCode = HttpStatus.OK;         String expectedResponseBody = "{\"someProperty\" : \"someValue\" }";          HttpHeaders headers = new HttpHeaders();         headers.add("Authorization", "Bearer YourTokenJwtForExample");          HttpEntity<String> entity = new HttpEntity<>(null, headers);          TestRestTemplate restTemplate = new TestRestTemplate();         ResponseEntity<String> response = restTemplate.exchange(             "http://localhost:" + port + "/my-rest-uri",             HttpMethod.GET, entity, String.class);          Assert.assertEquals(expectedStatusCode, response.getStatusCode());         Assert.assertEquals(expectedResponseBody, response.getBody());     }  } 
like image 25
Paulo Merson Avatar answered Sep 21 '22 21:09

Paulo Merson