diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index ae42cb4c121..5f2db3d3764 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -140,6 +140,14 @@ public class MockHttpServletResponse implements HttpServletResponse { return this.writerAccessAllowed; } + /** + * Return whether the character encoding has been set. + *

If {@code false}, {@link #getCharacterEncoding()} will return a default encoding value. + */ + public boolean isCharset() { + return charset; + } + @Override public void setCharacterEncoding(String characterEncoding) { this.characterEncoding = characterEncoding; diff --git a/spring-test/src/main/java/org/springframework/test/util/XpathExpectationsHelper.java b/spring-test/src/main/java/org/springframework/test/util/XpathExpectationsHelper.java index f506a9accbb..c686cd0d829 100644 --- a/spring-test/src/main/java/org/springframework/test/util/XpathExpectationsHelper.java +++ b/spring-test/src/main/java/org/springframework/test/util/XpathExpectationsHelper.java @@ -16,9 +16,13 @@ package org.springframework.test.util; -import java.io.StringReader; +import static org.hamcrest.MatcherAssert.*; +import static org.springframework.test.util.AssertionErrors.*; + +import java.io.ByteArrayInputStream; import java.util.Collections; import java.util.Map; + import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -35,11 +39,9 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.springframework.util.xml.SimpleNamespaceContext; -import static org.hamcrest.MatcherAssert.*; -import static org.springframework.test.util.AssertionErrors.*; - /** * A helper class for applying assertions via XPath expressions. * @@ -93,8 +95,8 @@ public class XpathExpectationsHelper { * Parse the content, evaluate the XPath expression as a {@link Node}, and * assert it with the given {@code Matcher}. */ - public void assertNode(String content, final Matcher matcher) throws Exception { - Document document = parseXmlString(content); + public void assertNode(byte[] content, String encoding, final Matcher matcher) throws Exception { + Document document = parseXmlByteArray(content, encoding); Node node = evaluateXpath(document, XPathConstants.NODE, Node.class); assertThat("XPath " + this.expression, node, matcher); } @@ -102,14 +104,18 @@ public class XpathExpectationsHelper { /** * Parse the given XML content to a {@link Document}. * @param xml the content to parse + * @param encoding optional content encoding, if provided as metadata (e.g. in HTTP headers) * @return the parsed document - * @throws Exception in case of errors + * @throws Exception */ - protected Document parseXmlString(String xml) throws Exception { + protected Document parseXmlByteArray(byte[] xml, String encoding) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(this.hasNamespaces); DocumentBuilder documentBuilder = factory.newDocumentBuilder(); - InputSource inputSource = new InputSource(new StringReader(xml)); + InputSource inputSource = new InputSource(new ByteArrayInputStream(xml)); + if(StringUtils.hasText(encoding)) { + inputSource.setEncoding(encoding); + } return documentBuilder.parse(inputSource); } @@ -128,8 +134,8 @@ public class XpathExpectationsHelper { * Apply the XPath expression and assert the resulting content exists. * @throws Exception if content parsing or expression evaluation fails */ - public void exists(String content) throws Exception { - Document document = parseXmlString(content); + public void exists(byte[] content, String encoding) throws Exception { + Document document = parseXmlByteArray(content, encoding); Node node = evaluateXpath(document, XPathConstants.NODE, Node.class); assertTrue("XPath " + this.expression + " does not exist", node != null); } @@ -138,8 +144,8 @@ public class XpathExpectationsHelper { * Apply the XPath expression and assert the resulting content does not exist. * @throws Exception if content parsing or expression evaluation fails */ - public void doesNotExist(String content) throws Exception { - Document document = parseXmlString(content); + public void doesNotExist(byte[] content, String encoding) throws Exception { + Document document = parseXmlByteArray(content, encoding); Node node = evaluateXpath(document, XPathConstants.NODE, Node.class); assertTrue("XPath " + this.expression + " exists", node == null); } @@ -149,8 +155,8 @@ public class XpathExpectationsHelper { * given Hamcrest matcher. * @throws Exception if content parsing or expression evaluation fails */ - public void assertNodeCount(String content, Matcher matcher) throws Exception { - Document document = parseXmlString(content); + public void assertNodeCount(byte[] content, String encoding, Matcher matcher) throws Exception { + Document document = parseXmlByteArray(content, encoding); NodeList nodeList = evaluateXpath(document, XPathConstants.NODESET, NodeList.class); assertThat("nodeCount for XPath " + this.expression, nodeList.getLength(), matcher); } @@ -159,8 +165,8 @@ public class XpathExpectationsHelper { * Apply the XPath expression and assert the resulting content as an integer. * @throws Exception if content parsing or expression evaluation fails */ - public void assertNodeCount(String content, int expectedCount) throws Exception { - Document document = parseXmlString(content); + public void assertNodeCount(byte[] content, String encoding, int expectedCount) throws Exception { + Document document = parseXmlByteArray(content, encoding); NodeList nodeList = evaluateXpath(document, XPathConstants.NODESET, NodeList.class); assertEquals("nodeCount for XPath " + this.expression, expectedCount, nodeList.getLength()); } @@ -170,8 +176,8 @@ public class XpathExpectationsHelper { * given Hamcrest matcher. * @throws Exception if content parsing or expression evaluation fails */ - public void assertString(String content, Matcher matcher) throws Exception { - Document document = parseXmlString(content); + public void assertString(byte[] content, String encoding, Matcher matcher) throws Exception { + Document document = parseXmlByteArray(content, encoding); String result = evaluateXpath(document, XPathConstants.STRING, String.class); assertThat("XPath " + this.expression, result, matcher); } @@ -180,8 +186,8 @@ public class XpathExpectationsHelper { * Apply the XPath expression and assert the resulting content as a String. * @throws Exception if content parsing or expression evaluation fails */ - public void assertString(String content, String expectedValue) throws Exception { - Document document = parseXmlString(content); + public void assertString(byte[] content, String encoding, String expectedValue) throws Exception { + Document document = parseXmlByteArray(content, encoding); String actual = evaluateXpath(document, XPathConstants.STRING, String.class); assertEquals("XPath " + this.expression, expectedValue, actual); } @@ -191,8 +197,8 @@ public class XpathExpectationsHelper { * given Hamcrest matcher. * @throws Exception if content parsing or expression evaluation fails */ - public void assertNumber(String content, Matcher matcher) throws Exception { - Document document = parseXmlString(content); + public void assertNumber(byte[] content, String encoding, Matcher matcher) throws Exception { + Document document = parseXmlByteArray(content, encoding); Double result = evaluateXpath(document, XPathConstants.NUMBER, Double.class); assertThat("XPath " + this.expression, result, matcher); } @@ -201,8 +207,8 @@ public class XpathExpectationsHelper { * Apply the XPath expression and assert the resulting content as a Double. * @throws Exception if content parsing or expression evaluation fails */ - public void assertNumber(String content, Double expectedValue) throws Exception { - Document document = parseXmlString(content); + public void assertNumber(byte[] content, String encoding, Double expectedValue) throws Exception { + Document document = parseXmlByteArray(content, encoding); Double actual = evaluateXpath(document, XPathConstants.NUMBER, Double.class); assertEquals("XPath " + this.expression, expectedValue, actual); } @@ -211,8 +217,8 @@ public class XpathExpectationsHelper { * Apply the XPath expression and assert the resulting content as a Boolean. * @throws Exception if content parsing or expression evaluation fails */ - public void assertBoolean(String content, boolean expectedValue) throws Exception { - Document document = parseXmlString(content); + public void assertBoolean(byte[] content, String encoding, boolean expectedValue) throws Exception { + Document document = parseXmlByteArray(content, encoding); String actual = evaluateXpath(document, XPathConstants.STRING, String.class); assertEquals("XPath " + this.expression, expectedValue, Boolean.parseBoolean(actual)); } diff --git a/spring-test/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java b/spring-test/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java index b16f2bf9d11..4f32e158b77 100644 --- a/spring-test/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,8 @@ import org.springframework.test.web.client.RequestMatcher; */ public class XpathRequestMatchers { + private static final String DEFAULT_ENCODING = "UTF-8"; + private final XpathExpectationsHelper xpathHelper; @@ -65,7 +67,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertNode(request.getBodyAsString(), matcher); + xpathHelper.assertNode(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); } }; } @@ -77,7 +79,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.exists(request.getBodyAsString()); + xpathHelper.exists(request.getBodyAsBytes(), DEFAULT_ENCODING); } }; } @@ -89,7 +91,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.doesNotExist(request.getBodyAsString()); + xpathHelper.doesNotExist(request.getBodyAsBytes(), DEFAULT_ENCODING); } }; } @@ -102,7 +104,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertNodeCount(request.getBodyAsString(), matcher); + xpathHelper.assertNodeCount(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); } }; } @@ -114,7 +116,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertNodeCount(request.getBodyAsString(), expectedCount); + xpathHelper.assertNodeCount(request.getBodyAsBytes(), DEFAULT_ENCODING, expectedCount); } }; } @@ -126,7 +128,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertString(request.getBodyAsString(), matcher); + xpathHelper.assertString(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); } }; } @@ -138,7 +140,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertString(request.getBodyAsString(), value); + xpathHelper.assertString(request.getBodyAsBytes(), DEFAULT_ENCODING, value); } }; } @@ -150,7 +152,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertNumber(request.getBodyAsString(), matcher); + xpathHelper.assertNumber(request.getBodyAsBytes(), DEFAULT_ENCODING, matcher); } }; } @@ -162,7 +164,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertNumber(request.getBodyAsString(), value); + xpathHelper.assertNumber(request.getBodyAsBytes(), DEFAULT_ENCODING, value); } }; } @@ -174,7 +176,7 @@ public class XpathRequestMatchers { return new AbstractXpathRequestMatcher() { @Override protected void matchInternal(MockClientHttpRequest request) throws Exception { - xpathHelper.assertBoolean(request.getBodyAsString(), value); + xpathHelper.assertBoolean(request.getBodyAsBytes(), DEFAULT_ENCODING, value); } }; } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java index 922f2b82830..eb1ebeb5307 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/XpathResultMatchers.java @@ -22,6 +22,7 @@ import javax.xml.xpath.XPathExpressionException; import org.hamcrest.Matcher; import org.w3c.dom.Node; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.util.XpathExpectationsHelper; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultMatcher; @@ -62,12 +63,19 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertNode(content, matcher); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertNode(response.getContentAsByteArray(), getDefinedEncoding(response), matcher); } }; } + /** + * Get the response encoding if explicitely defined in the response, null otherwise + */ + private String getDefinedEncoding(MockHttpServletResponse response) { + return response.isCharset() ? response.getCharacterEncoding() : null; + } + /** * Evaluate the XPath and assert that content exists. */ @@ -75,8 +83,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.exists(content); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.exists(response.getContentAsByteArray(), getDefinedEncoding(response)); } }; } @@ -88,8 +96,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.doesNotExist(content); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.doesNotExist(response.getContentAsByteArray(), getDefinedEncoding(response)); } }; } @@ -102,8 +110,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertNodeCount(content, matcher); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertNodeCount(response.getContentAsByteArray(), getDefinedEncoding(response), matcher); } }; } @@ -115,8 +123,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertNodeCount(content, expectedCount); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertNodeCount(response.getContentAsByteArray(), getDefinedEncoding(response), expectedCount); } }; } @@ -129,8 +137,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertString(content, matcher); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertString(response.getContentAsByteArray(), getDefinedEncoding(response), matcher); } }; } @@ -142,8 +150,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertString(content, expectedValue); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertString(response.getContentAsByteArray(), getDefinedEncoding(response), expectedValue); } }; } @@ -156,8 +164,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertNumber(content, matcher); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertNumber(response.getContentAsByteArray(), getDefinedEncoding(response), matcher); } }; } @@ -169,8 +177,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertNumber(content, expectedValue); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertNumber(response.getContentAsByteArray(), getDefinedEncoding(response), expectedValue); } }; } @@ -182,8 +190,8 @@ public class XpathResultMatchers { return new ResultMatcher() { @Override public void match(MvcResult result) throws Exception { - String content = result.getResponse().getContentAsString(); - xpathHelper.assertBoolean(content, value); + MockHttpServletResponse response = result.getResponse(); + xpathHelper.assertBoolean(response.getContentAsByteArray(), getDefinedEncoding(response), value); } }; } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/XpathResultMatchersTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/XpathResultMatchersTests.java index 962040a2089..08010cfb5e6 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/XpathResultMatchersTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/XpathResultMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,15 @@ */ package org.springframework.test.web.servlet.result; +import java.nio.charset.Charset; + import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.test.web.servlet.StubMvcResult; +import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; /** * Tests for {@link XpathResultMatchers}. @@ -98,12 +102,25 @@ public class XpathResultMatchersTests { new XpathResultMatchers("/foo/bar[2]", null).booleanValue(false).match(getStubMvcResult()); } + @Test + public void testStringEncodingDetection() throws Exception { + String content = "\n" + + "Jürgen"; + byte[] bytes = content.getBytes(Charset.forName("UTF-8")); + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/xml"); + StreamUtils.copy(bytes, response.getOutputStream()); + StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response); + + new XpathResultMatchers("/person/name", null).string("Jürgen").match(result); + } + private static final String RESPONSE_CONTENT = "111true"; private StubMvcResult getStubMvcResult() throws Exception { MockHttpServletResponse response = new MockHttpServletResponse(); - response.addHeader("Content-Type", "application/json"); + response.addHeader("Content-Type", "application/xml"); response.getWriter().print(new String(RESPONSE_CONTENT.getBytes("ISO-8859-1"))); return new StubMvcResult(null, null, null, null, null, null, response); }