View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.test.web.servlet.samples.standalone.resultmatchers;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.List;
22  import java.util.Map;
23  import javax.xml.bind.annotation.XmlAccessType;
24  import javax.xml.bind.annotation.XmlAccessorType;
25  import javax.xml.bind.annotation.XmlElement;
26  import javax.xml.bind.annotation.XmlElementWrapper;
27  import javax.xml.bind.annotation.XmlRootElement;
28  
29  import org.junit.Before;
30  import org.junit.Test;
31  
32  import org.springframework.http.MediaType;
33  import org.springframework.stereotype.Controller;
34  import org.springframework.test.web.Person;
35  import org.springframework.test.web.servlet.MockMvc;
36  import org.springframework.web.bind.annotation.RequestMapping;
37  import org.springframework.web.bind.annotation.ResponseBody;
38  
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  
45  /**
46   * Examples of expectations on XML response content with XPath expressions.
47   *
48   * @author Rossen Stoyanchev
49   * @author Sam Brannen
50   * @see ContentAssertionTests
51   * @see XmlContentAssertionTests
52   */
53  public class XpathAssertionTests {
54  
55  	private static final Map<String, String> musicNamespace =
56  		Collections.singletonMap("ns", "http://example.org/music/people");
57  
58  	private MockMvc mockMvc;
59  
60  	@Before
61  	public void setup() throws Exception {
62  		this.mockMvc = standaloneSetup(new MusicController())
63  				.defaultRequest(get("/").accept(MediaType.APPLICATION_XML, MediaType.parseMediaType("application/xml;charset=UTF-8")))
64  				.alwaysExpect(status().isOk())
65  				.alwaysExpect(content().contentType(MediaType.parseMediaType("application/xml;charset=UTF-8")))
66  				.build();
67  	}
68  
69  	@Test
70  	public void testExists() throws Exception {
71  
72  		String composer = "/ns:people/composers/composer[%s]";
73  		String performer = "/ns:people/performers/performer[%s]";
74  
75  		this.mockMvc.perform(get("/music/people"))
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()));
83  	}
84  
85  	@Test
86  	public void testDoesNotExist() throws Exception {
87  
88  		String composer = "/ns:people/composers/composer[%s]";
89  		String performer = "/ns:people/performers/performer[%s]";
90  
91  		this.mockMvc.perform(get("/music/people"))
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()));
97  	}
98  
99  	@Test
100 	public void testString() throws Exception {
101 
102 		String composerName = "/ns:people/composers/composer[%s]/name";
103 		String performerName = "/ns:people/performers/performer[%s]/name";
104 
105 		this.mockMvc.perform(get("/music/people"))
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()));
115 	}
116 
117 	@Test
118 	public void testNumber() throws Exception {
119 
120 		String composerDouble = "/ns:people/composers/composer[%s]/someDouble";
121 
122 		this.mockMvc.perform(get("/music/people"))
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)));
129 	}
130 
131 	@Test
132 	public void testBoolean() throws Exception {
133 
134 		String performerBooleanValue = "/ns:people/performers/performer[%s]/someBoolean";
135 
136 		this.mockMvc.perform(get("/music/people"))
137 			.andExpect(xpath(performerBooleanValue, musicNamespace, 1).booleanValue(false))
138 			.andExpect(xpath(performerBooleanValue, musicNamespace, 2).booleanValue(true));
139 	}
140 
141 	@Test
142 	public void testNodeCount() throws Exception {
143 
144 		this.mockMvc.perform(get("/music/people"))
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"));
164 	}
165 
166 
167 	@Controller
168 	private static class MusicController {
169 
170 		@RequestMapping(value="/music/people")
171 		public @ResponseBody PeopleWrapper getPeople() {
172 
173 			List<Person> composers = Arrays.asList(
174 					new Person("Johann Sebastian Bach").setSomeDouble(21),
175 					new Person("Johannes Brahms").setSomeDouble(.0025),
176 					new Person("Edvard Grieg").setSomeDouble(1.6035),
177 					new Person("Robert Schumann").setSomeDouble(Double.NaN));
178 
179 			List<Person> performers = Arrays.asList(
180 					new Person("Vladimir Ashkenazy").setSomeBoolean(false),
181 					new Person("Yehudi Menuhin").setSomeBoolean(true));
182 
183 			return new PeopleWrapper(composers, performers);
184 		}
185 	}
186 
187 	@SuppressWarnings("unused")
188 	@XmlRootElement(name="people", namespace="http://example.org/music/people")
189 	@XmlAccessorType(XmlAccessType.FIELD)
190 	private static class PeopleWrapper {
191 
192 		@XmlElementWrapper(name="composers")
193 		@XmlElement(name="composer")
194 		private List<Person> composers;
195 
196 		@XmlElementWrapper(name="performers")
197 		@XmlElement(name="performer")
198 		private List<Person> performers;
199 
200 		public PeopleWrapper() {
201 		}
202 
203 		public PeopleWrapper(List<Person> composers, List<Person> performers) {
204 			this.composers = composers;
205 			this.performers = performers;
206 		}
207 
208 		public List<Person> getComposers() {
209 			return this.composers;
210 		}
211 
212 		public List<Person> getPerformers() {
213 			return this.performers;
214 		}
215 	}
216 
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 
232 }