Skip to content

Commit 7bab879

Browse files
committed
Fix issue with xpath assertions in Spring Test MVC
Issue: SPR-10704
1 parent 82ec06a commit 7bab879

File tree

2 files changed

+79
-49
lines changed

2 files changed

+79
-49
lines changed

spring-test-mvc/src/main/java/org/springframework/test/util/XpathExpectationsHelper.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.test.util;
1818

19-
import static org.springframework.test.util.AssertionErrors.*;
20-
import static org.springframework.test.util.MatcherAssertionErrors.*;
21-
2219
import java.io.StringReader;
2320
import java.util.Collections;
2421
import java.util.Map;
@@ -33,12 +30,16 @@
3330
import javax.xml.xpath.XPathFactory;
3431

3532
import org.hamcrest.Matcher;
33+
import org.springframework.util.CollectionUtils;
3634
import org.springframework.util.xml.SimpleNamespaceContext;
3735
import org.w3c.dom.Document;
3836
import org.w3c.dom.Node;
3937
import org.w3c.dom.NodeList;
4038
import org.xml.sax.InputSource;
4139

40+
import static org.springframework.test.util.AssertionErrors.*;
41+
import static org.springframework.test.util.MatcherAssertionErrors.*;
42+
4243
/**
4344
* A helper class for applying assertions via XPath expressions.
4445
*
@@ -51,6 +52,8 @@ public class XpathExpectationsHelper {
5152

5253
private final XPathExpression xpathExpression;
5354

55+
private final boolean hasNamespaces;
56+
5457

5558
/**
5659
* Class constructor.
@@ -66,6 +69,7 @@ public XpathExpectationsHelper(String expression, Map<String, String> namespaces
6669

6770
this.expression = String.format(expression, args);
6871
this.xpathExpression = compileXpathExpression(this.expression, namespaces);
72+
this.hasNamespaces = !CollectionUtils.isEmpty(namespaces);
6973
}
7074

7175
private XPathExpression compileXpathExpression(String expression, Map<String, String> namespaces)
@@ -104,7 +108,7 @@ public void assertNode(String content, final Matcher<? super Node> matcher) thro
104108
*/
105109
protected Document parseXmlString(String xml) throws Exception {
106110
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
107-
factory.setNamespaceAware(true);
111+
factory.setNamespaceAware(this.hasNamespaces);
108112
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
109113
InputSource inputSource = new InputSource(new StringReader(xml));
110114
Document document = documentBuilder.parse(inputSource);

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

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,6 @@
1616

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

19-
import static org.hamcrest.Matchers.closeTo;
20-
import static org.hamcrest.Matchers.equalTo;
21-
import static org.hamcrest.Matchers.notNullValue;
22-
import static org.hamcrest.Matchers.nullValue;
23-
import static org.hamcrest.Matchers.startsWith;
24-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
25-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
26-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
27-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.xpath;
28-
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
29-
3019
import java.util.Arrays;
3120
import java.util.Collections;
3221
import java.util.List;
@@ -47,6 +36,12 @@
4736
import org.springframework.web.bind.annotation.RequestMapping;
4837
import org.springframework.web.bind.annotation.ResponseBody;
4938

39+
import static org.hamcrest.Matchers.*;
40+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
41+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
42+
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
43+
import static org.springframework.web.bind.annotation.RequestMethod.*;
44+
5045
/**
5146
* Examples of expectations on XML response content with XPath expressions.
5247
*
@@ -57,7 +52,7 @@
5752
*/
5853
public class XpathAssertionTests {
5954

60-
private static final Map<String, String> NS =
55+
private static final Map<String, String> musicNamespace =
6156
Collections.singletonMap("ns", "http://example.org/music/people");
6257

6358
private MockMvc mockMvc;
@@ -78,13 +73,13 @@ public void testExists() throws Exception {
7873
String performer = "/ns:people/performers/performer[%s]";
7974

8075
this.mockMvc.perform(get("/music/people"))
81-
.andExpect(xpath(composer, NS, 1).exists())
82-
.andExpect(xpath(composer, NS, 2).exists())
83-
.andExpect(xpath(composer, NS, 3).exists())
84-
.andExpect(xpath(composer, NS, 4).exists())
85-
.andExpect(xpath(performer, NS, 1).exists())
86-
.andExpect(xpath(performer, NS, 2).exists())
87-
.andExpect(xpath(composer, NS, 1).node(notNullValue()));
76+
.andExpect(xpath(composer, musicNamespace, 1).exists())
77+
.andExpect(xpath(composer, musicNamespace, 2).exists())
78+
.andExpect(xpath(composer, musicNamespace, 3).exists())
79+
.andExpect(xpath(composer, musicNamespace, 4).exists())
80+
.andExpect(xpath(performer, musicNamespace, 1).exists())
81+
.andExpect(xpath(performer, musicNamespace, 2).exists())
82+
.andExpect(xpath(composer, musicNamespace, 1).node(notNullValue()));
8883
}
8984

9085
@Test
@@ -94,11 +89,11 @@ public void testDoesNotExist() throws Exception {
9489
String performer = "/ns:people/performers/performer[%s]";
9590

9691
this.mockMvc.perform(get("/music/people"))
97-
.andExpect(xpath(composer, NS, 0).doesNotExist())
98-
.andExpect(xpath(composer, NS, 5).doesNotExist())
99-
.andExpect(xpath(performer, NS, 0).doesNotExist())
100-
.andExpect(xpath(performer, NS, 3).doesNotExist())
101-
.andExpect(xpath(composer, NS, 0).node(nullValue()));
92+
.andExpect(xpath(composer, musicNamespace, 0).doesNotExist())
93+
.andExpect(xpath(composer, musicNamespace, 5).doesNotExist())
94+
.andExpect(xpath(performer, musicNamespace, 0).doesNotExist())
95+
.andExpect(xpath(performer, musicNamespace, 3).doesNotExist())
96+
.andExpect(xpath(composer, musicNamespace, 0).node(nullValue()));
10297
}
10398

10499
@Test
@@ -108,15 +103,15 @@ public void testString() throws Exception {
108103
String performerName = "/ns:people/performers/performer[%s]/name";
109104

110105
this.mockMvc.perform(get("/music/people"))
111-
.andExpect(xpath(composerName, NS, 1).string("Johann Sebastian Bach"))
112-
.andExpect(xpath(composerName, NS, 2).string("Johannes Brahms"))
113-
.andExpect(xpath(composerName, NS, 3).string("Edvard Grieg"))
114-
.andExpect(xpath(composerName, NS, 4).string("Robert Schumann"))
115-
.andExpect(xpath(performerName, NS, 1).string("Vladimir Ashkenazy"))
116-
.andExpect(xpath(performerName, NS, 2).string("Yehudi Menuhin"))
117-
.andExpect(xpath(composerName, NS, 1).string(equalTo("Johann Sebastian Bach"))) // Hamcrest..
118-
.andExpect(xpath(composerName, NS, 1).string(startsWith("Johann")))
119-
.andExpect(xpath(composerName, NS, 1).string(notNullValue()));
106+
.andExpect(xpath(composerName, musicNamespace, 1).string("Johann Sebastian Bach"))
107+
.andExpect(xpath(composerName, musicNamespace, 2).string("Johannes Brahms"))
108+
.andExpect(xpath(composerName, musicNamespace, 3).string("Edvard Grieg"))
109+
.andExpect(xpath(composerName, musicNamespace, 4).string("Robert Schumann"))
110+
.andExpect(xpath(performerName, musicNamespace, 1).string("Vladimir Ashkenazy"))
111+
.andExpect(xpath(performerName, musicNamespace, 2).string("Yehudi Menuhin"))
112+
.andExpect(xpath(composerName, musicNamespace, 1).string(equalTo("Johann Sebastian Bach"))) // Hamcrest..
113+
.andExpect(xpath(composerName, musicNamespace, 1).string(startsWith("Johann")))
114+
.andExpect(xpath(composerName, musicNamespace, 1).string(notNullValue()));
120115
}
121116

122117
@Test
@@ -125,12 +120,12 @@ public void testNumber() throws Exception {
125120
String composerDouble = "/ns:people/composers/composer[%s]/someDouble";
126121

127122
this.mockMvc.perform(get("/music/people"))
128-
.andExpect(xpath(composerDouble, NS, 1).number(21d))
129-
.andExpect(xpath(composerDouble, NS, 2).number(.0025))
130-
.andExpect(xpath(composerDouble, NS, 3).number(1.6035))
131-
.andExpect(xpath(composerDouble, NS, 4).number(Double.NaN))
132-
.andExpect(xpath(composerDouble, NS, 1).number(equalTo(21d))) // Hamcrest..
133-
.andExpect(xpath(composerDouble, NS, 3).number(closeTo(1.6, .01)));
123+
.andExpect(xpath(composerDouble, musicNamespace, 1).number(21d))
124+
.andExpect(xpath(composerDouble, musicNamespace, 2).number(.0025))
125+
.andExpect(xpath(composerDouble, musicNamespace, 3).number(1.6035))
126+
.andExpect(xpath(composerDouble, musicNamespace, 4).number(Double.NaN))
127+
.andExpect(xpath(composerDouble, musicNamespace, 1).number(equalTo(21d))) // Hamcrest..
128+
.andExpect(xpath(composerDouble, musicNamespace, 3).number(closeTo(1.6, .01)));
134129
}
135130

136131
@Test
@@ -139,20 +134,36 @@ public void testBoolean() throws Exception {
139134
String performerBooleanValue = "/ns:people/performers/performer[%s]/someBoolean";
140135

141136
this.mockMvc.perform(get("/music/people"))
142-
.andExpect(xpath(performerBooleanValue, NS, 1).booleanValue(false))
143-
.andExpect(xpath(performerBooleanValue, NS, 2).booleanValue(true));
137+
.andExpect(xpath(performerBooleanValue, musicNamespace, 1).booleanValue(false))
138+
.andExpect(xpath(performerBooleanValue, musicNamespace, 2).booleanValue(true));
144139
}
145140

146141
@Test
147142
public void testNodeCount() throws Exception {
148143

149144
this.mockMvc.perform(get("/music/people"))
150-
.andExpect(xpath("/ns:people/composers/composer", NS).nodeCount(4))
151-
.andExpect(xpath("/ns:people/performers/performer", NS).nodeCount(2))
152-
.andExpect(xpath("/ns:people/composers/composer", NS).nodeCount(equalTo(4))) // Hamcrest..
153-
.andExpect(xpath("/ns:people/performers/performer", NS).nodeCount(equalTo(2)));
145+
.andExpect(xpath("/ns:people/composers/composer", musicNamespace).nodeCount(4))
146+
.andExpect(xpath("/ns:people/performers/performer", musicNamespace).nodeCount(2))
147+
.andExpect(xpath("/ns:people/composers/composer", musicNamespace).nodeCount(equalTo(4))) // Hamcrest..
148+
.andExpect(xpath("/ns:people/performers/performer", musicNamespace).nodeCount(equalTo(2)));
149+
}
150+
151+
// SPR-10704
152+
153+
@Test
154+
public void testFeedWithLinefeedChars() throws Exception {
155+
156+
// Map<String, String> namespace = Collections.singletonMap("ns", "");
157+
158+
standaloneSetup(new BlogFeedController()).build()
159+
.perform(get("/blog.atom").accept(MediaType.APPLICATION_ATOM_XML))
160+
.andExpect(status().isOk())
161+
.andExpect(content().contentType(MediaType.APPLICATION_ATOM_XML))
162+
.andExpect(xpath("//feed/title").string("Test Feed"))
163+
.andExpect(xpath("//feed/icon").string("http://www.example.com/favicon.ico"));
154164
}
155165

166+
156167
@Controller
157168
private static class MusicController {
158169

@@ -203,4 +214,19 @@ public List<Person> getPerformers() {
203214
}
204215
}
205216

217+
218+
@Controller
219+
public class BlogFeedController {
220+
221+
@RequestMapping(value="/blog.atom", method = { GET, HEAD })
222+
@ResponseBody
223+
public String listPublishedPosts() {
224+
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
225+
+ "<feed xmlns=\"http://www.w3.org/2005/Atom\">\r\n"
226+
+ " <title>Test Feed</title>\r\n"
227+
+ " <icon>http://www.example.com/favicon.ico</icon>\r\n"
228+
+ "</feed>\r\n\r\n";
229+
}
230+
}
231+
206232
}

0 commit comments

Comments
 (0)