Что нужно для того, чтобы mockMVC тестировал процедуру инициализации фильтра?

Я реализовал следующий фильтр CORS, который работает, когда код выполняется на сервере:

/*
 *    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() {
    }
}

В следующем тесте JUnit используется mockMVC, но он не работает, потому что логика "init" CORSFilter не выполняется (проверено путем остановки теста JUnit):

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();      
        }
    }
}

Я просмотрел доступный материал в Интернете, который, по-видимому, подразумевает ".addfilter(filter). Элемент секции mockMVC @Before должен выполнять процедуру инициализации CORSFilter. Однако это явно НЕ происходит.

Приветствуются любые предложения или рекомендации, так как я действительно застрял в понимании того, как получить процедуру "init", протестированную с использованием возможностей mockMVC.

Ответ 1

Набор тестов Spring MVC Test не предназначен для тестирования конфигурации контейнера, он предназначен для проверки конфигурации MVC (@Controller и других сопоставлений). Filter#init(ServletConfig) - метод, управляемый контейнером.

Если вам действительно нужно протестировать его, вы тоже можете издеваться над этим

@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();
}

Ответ 2

Если вы хотите использовать unit test вместо теста интеграции, вы можете также взглянуть на org.springframework.mock.web.MockServletConfig, доступный из

org.springframework:spring-test артефакт maven

Вы можете настроить параметры конфигурации для макетного объекта. Есть также mocks для HttpServletRequest, HttpServletResponse и FilterChain

Ответ 3

Для приложения Spring Boot, если используется @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT), тогда filter.init() вызывается автоматически. если @SpringBootTest используется с параметрами по умолчанию, тогда filter.init() необходимо вызвать вручную.