thymeleaf / thymeleaf-testing

Thymeleaf testing infrastructure
Apache License 2.0
77 stars 26 forks source link

Ability to pass application context, servlet context, session, request and response to TestExecutor #12

Open dtrunk90 opened 8 years ago

dtrunk90 commented 8 years ago

It's currently not supported to use java config out of the box. You need to pass the mocks created from Spring to the TestExecutor which is only possible by a custom implementation of IProcessingContextBuilder:

@WebAppConfiguration
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTest {
    @Configuration
    public static class ContextConfiguration {
        // some beans
    }

    @Autowired
    private WebApplicationContext applicationContext;

    @Autowired
    private MockServletContext servletContext;

    @Autowired
    private MockHttpServletRequest request;

    @Autowired
    private MockHttpServletResponse response;

    @Autowired
    private ServletWebRequest webRequest;

    private TestExecutor executor;

    @Before
    public void setUp() {
        IProcessingContextBuilder processingContextBuilder = new CustomContextBuilder(applicationContext, servletContext, session, request, response, webRequest);

        List<IDialect> dialects = new ArrayList<IDialect>();
        dialects.add(new SpringStandardDialect());

        executor = new TestExecutor();
        executor.setProcessingContextBuilder(processingContextBuilder);
        executor.setDialects(dialects);
    }

    // some tests
}

And this is where the trouble begins: You cannot simply extend from SpringWebProcessingContextBuilder as most of the methods createApplicationContext, createMockServletContext, createMockHttpServletRequest, createMockHttpServletResponse are final and the ServletWebRequest will be initialized directly in doAdditionalVariableProcessing instead of an own method.

So I ended up in copying all the stuff of the classes which works but is really ugly:

public class CustomContextBuilder implements IProcessingContextBuilder {
    public static final Locale DEFAULT_LOCALE = Locale.ENGLISH;

    private static final String REQUEST_PARAMS_PREFIX = "param";
    private static final String REQUEST_ATTRS_PREFIX = "request";
    private static final String SESSION_ATTRS_PREFIX = "session";
    private static final String SERVLETCONTEXT_ATTRS_PREFIX = "application";

    private final WebApplicationContext applicationContext;
    private final ServletContext servletContext;
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    private final ServletWebRequest webRequest;

    public JawrDialectContextBuilder(WebApplicationContext applicationContext, ServletContext servletContext, HttpServletRequest request, HttpServletResponse response, ServletWebRequest webRequest) {
        this.applicationContext = applicationContext;
        this.servletContext = servletContext;
        this.request = request;
        this.response = response;
        this.webRequest = webRequest;
    }

    @Override
    public IContext build(ITest test) {
        if (test == null) {
            return null;
        }

        ITestContext testContext = test.getContext();

        Locale locale = DEFAULT_LOCALE;
        ITestContextExpression localeExpression = testContext.getLocale();
        if (localeExpression != null) {
            Object exprResult = localeExpression.evaluate(Collections.<String, Object> emptyMap(), DEFAULT_LOCALE);
            if (exprResult != null) {
                locale = LocaleUtils.toLocale(exprResult.toString());
            }
        }

        Map<String, Object> variables = new HashMap<String, Object>();

        Map<String, Object[]> requestParameters = new LinkedHashMap<String, Object[]>();
        variables.put(REQUEST_PARAMS_PREFIX, requestParameters);

        Map<String, Object> requestAttributes = new LinkedHashMap<String, Object>();
        variables.put(REQUEST_ATTRS_PREFIX, requestAttributes);

        Map<String, Object> sessionAttributes = new LinkedHashMap<String, Object>();
        variables.put(SESSION_ATTRS_PREFIX, sessionAttributes);

        Map<String, Object> servletContextAttributes = new LinkedHashMap<String, Object>();
        variables.put(SERVLETCONTEXT_ATTRS_PREFIX, servletContextAttributes);

        for (Map.Entry<String, ITestContextExpression> entry : testContext.getVariables().entrySet()) {
            resolve(entry.getKey(), entry.getValue(), variables, locale);
        }

        for (Map.Entry<String, ITestContextExpression[]> entry : testContext.getRequestParameters().entrySet()) {
            int firstPoint = entry.getKey().indexOf('.');

            String paramName = firstPoint == -1 ? entry.getKey() : entry.getKey().substring(0, firstPoint);
            Object[] paramValues = new Object[entry.getValue().length];

            requestParameters.put(paramName, paramValues);

            String remainder = firstPoint == -1 ? "" : entry.getKey().substring(firstPoint);
            int expressionsLen = entry.getValue().length;

            for (int i = 0; i < expressionsLen; i++) {
                resolve(REQUEST_PARAMS_PREFIX + "." + paramName + "[" + i + "]" + remainder, entry.getValue()[i], variables, locale);
            }
        }

        for (Map.Entry<String, ITestContextExpression> entry : testContext.getRequestAttributes().entrySet()) {
            resolve(REQUEST_ATTRS_PREFIX + "." + entry.getKey(), entry.getValue(), variables, locale);
        }

        for (Map.Entry<String, ITestContextExpression> entry : testContext.getSessionAttributes().entrySet()) {
            resolve(SESSION_ATTRS_PREFIX + "." + entry.getKey(), entry.getValue(), variables, locale);
        }

        for (Map.Entry<String, ITestContextExpression> entry : testContext.getServletContextAttributes().entrySet()) {
            resolve(SERVLETCONTEXT_ATTRS_PREFIX + "." + entry.getKey(),entry.getValue(), variables, locale);
        }

        variables.remove(REQUEST_PARAMS_PREFIX);
        variables.remove(REQUEST_ATTRS_PREFIX);
        variables.remove(SESSION_ATTRS_PREFIX);
        variables.remove(SERVLETCONTEXT_ATTRS_PREFIX);

        doAdditionalVariableProcessing(test, request, response, servletContext, locale, variables);
        return new WebContext(request, response, servletContext, locale, variables);
    }

    private void resolve(String expression, ITestContextExpression contextExpression, Map<String, Object> variables, Locale locale) {
        try {
            Object result = contextExpression.evaluate(variables, locale);

            Object parsedExpression = Ognl.parseExpression(expression);
            Ognl.setValue(parsedExpression, variables, result);
        } catch (Throwable t) {
            throw new TestEngineExecutionException("Exception while trying to evaluate expression \"" + expression + "\" on context for test \"" + TestExecutor.getThreadTestName() + "\"", t);
        }
    }

    private void doAdditionalVariableProcessing(ITest test, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext, Locale locale, Map<String, Object> variables) {
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext);

        request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext);

        RequestContextHolder.setRequestAttributes(webRequest);

        ConversionService conversionService = getConversionService(applicationContext);

        RequestContext requestContext = new RequestContext(request, response, servletContext, variables);
        variables.put(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, requestContext);

        additionalVariableProcessing(applicationContext, conversionService, variables);

        initializeBindingResults(test, conversionService, locale, variables);

    }

    private ConversionService getConversionService(ApplicationContext applicationContext) {
        if (applicationContext == null) {
            return null;
        }

        Map<String, ConversionService> conversionServices = applicationContext.getBeansOfType(ConversionService.class);
        if (conversionServices.size() == 0) {
            return null;
        }

        return (ConversionService) conversionServices.values().toArray()[0];
    }

    private void additionalVariableProcessing(ApplicationContext applicationContext, ConversionService conversionService, Map<String, Object> variables) {
        RequestContext requestContext = (RequestContext) variables.get(AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE);
        variables.put(SpringContextVariableNames.SPRING_REQUEST_CONTEXT, requestContext);

        ThymeleafEvaluationContext evaluationContext = new ThymeleafEvaluationContext(applicationContext, conversionService);

        variables.put(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME, evaluationContext);
    }

    private void initializeBindingResults(ITest test, ConversionService conversionService, Locale locale, Map<String, Object> variables) {
        List<String> variableNames = new ArrayList<String>(variables.keySet());
        for (String variableName : variableNames) {
            Object bindingObject = variables.get(variableName);
            if (isBindingCandidate(variableName, bindingObject)) {
                String bindingVariableName = BindingResult.MODEL_KEY_PREFIX + variableName;
                if (!variables.containsKey(bindingVariableName)) {
                    WebDataBinder dataBinders = createBinding(test, variableName, bindingVariableName, bindingObject, conversionService, locale, variables);
                    variables.put(bindingVariableName, dataBinders.getBindingResult());
                }
            }
        }
    }

    private boolean isBindingCandidate(String variableName, Object bindingObject) {
        if (variableName.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
            return false;
        }

        return bindingObject != null && !bindingObject.getClass().isArray() && !(bindingObject instanceof Collection) && !(bindingObject instanceof Map) && !BeanUtils.isSimpleValueType(bindingObject.getClass());
    }

    private WebDataBinder createBinding(ITest test, String variableName, String bindingVariableName, Object bindingObject, ConversionService conversionService, Locale locale, Map<String, Object> variables) {
        WebDataBinder dataBinder = new WebDataBinder(bindingObject, bindingVariableName);
        dataBinder.setConversionService(conversionService);
        return dataBinder;
    }
}
dtrunk90 commented 8 years ago

Here's a working example: https://github.com/dtrunk90/thymeleaf-jawr-extension/tree/thymeleaf3/src/test/java/com/github/dtrunk90/thymeleaf/jawr/dialect/test