001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.web.servlet.result;
018
019import java.util.Map;
020import javax.servlet.http.HttpServletResponse;
021import javax.xml.transform.Source;
022import javax.xml.transform.dom.DOMSource;
023
024import org.hamcrest.Matcher;
025import org.w3c.dom.Node;
026
027import org.springframework.http.MediaType;
028import org.springframework.test.util.JsonExpectationsHelper;
029import org.springframework.test.util.XmlExpectationsHelper;
030import org.springframework.test.web.servlet.MvcResult;
031import org.springframework.test.web.servlet.ResultMatcher;
032
033import static org.hamcrest.MatcherAssert.*;
034import static org.springframework.test.util.AssertionErrors.*;
035
036/**
037 * Factory for response content assertions.
038 *
039 * <p>An instance of this class is typically accessed via
040 * {@link MockMvcResultMatchers#content}.
041 *
042 * @author Rossen Stoyanchev
043 * @since 3.2
044 */
045public class ContentResultMatchers {
046
047        private final XmlExpectationsHelper xmlHelper;
048
049        private final JsonExpectationsHelper jsonHelper;
050
051
052        /**
053         * Protected constructor.
054         * Use {@link MockMvcResultMatchers#content()}.
055         */
056        protected ContentResultMatchers() {
057                this.xmlHelper = new XmlExpectationsHelper();
058                this.jsonHelper = new JsonExpectationsHelper();
059        }
060
061
062        /**
063         * Assert the ServletResponse content type. The given content type must
064         * fully match including type, sub-type, and parameters. For checking
065         * only the type and sub-type see {@link #contentTypeCompatibleWith(String)}.
066         */
067        public ResultMatcher contentType(String contentType) {
068                return contentType(MediaType.parseMediaType(contentType));
069        }
070
071        /**
072         * Assert the ServletResponse content type after parsing it as a MediaType.
073         * The given content type must fully match including type, sub-type, and
074         * parameters. For checking only the type and sub-type see
075         * {@link #contentTypeCompatibleWith(MediaType)}.
076         */
077        public ResultMatcher contentType(final MediaType contentType) {
078                return new ResultMatcher() {
079                        @Override
080                        public void match(MvcResult result) throws Exception {
081                                String actual = result.getResponse().getContentType();
082                                assertTrue("Content type not set", actual != null);
083                                assertEquals("Content type", contentType, MediaType.parseMediaType(actual));
084                        }
085                };
086        }
087
088        /**
089         * Assert the ServletResponse content type is compatible with the given
090         * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}.
091         */
092        public ResultMatcher contentTypeCompatibleWith(String contentType) {
093                return contentTypeCompatibleWith(MediaType.parseMediaType(contentType));
094        }
095
096        /**
097         * Assert the ServletResponse content type is compatible with the given
098         * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}.
099         */
100        public ResultMatcher contentTypeCompatibleWith(final MediaType contentType) {
101                return new ResultMatcher() {
102                        @Override
103                        public void match(MvcResult result) throws Exception {
104                                String actual = result.getResponse().getContentType();
105                                assertTrue("Content type not set", actual != null);
106                                MediaType actualContentType = MediaType.parseMediaType(actual);
107                                assertTrue("Content type [" + actual + "] is not compatible with [" + contentType + "]",
108                                                actualContentType.isCompatibleWith(contentType));
109                        }
110                };
111        }
112
113        /**
114         * Assert the character encoding in the ServletResponse.
115         * @see HttpServletResponse#getCharacterEncoding()
116         */
117        public ResultMatcher encoding(final String characterEncoding) {
118                return new ResultMatcher() {
119                        @Override
120                        public void match(MvcResult result) {
121                                String actual = result.getResponse().getCharacterEncoding();
122                                assertEquals("Character encoding", characterEncoding, actual);
123                        }
124                };
125        }
126
127        /**
128         * Assert the response body content with a Hamcrest {@link Matcher}.
129         * <pre class="code">
130         * mockMvc.perform(get("/path"))
131         *   .andExpect(content().string(containsString("text")));
132         * </pre>
133         */
134        public ResultMatcher string(final Matcher<? super String> matcher) {
135                return new ResultMatcher() {
136                        @Override
137                        public void match(MvcResult result) throws Exception {
138                                assertThat("Response content", result.getResponse().getContentAsString(), matcher);
139                        }
140                };
141        }
142
143        /**
144         * Assert the response body content as a String.
145         */
146        public ResultMatcher string(final String expectedContent) {
147                return new ResultMatcher() {
148                        @Override
149                        public void match(MvcResult result) throws Exception {
150                                assertEquals("Response content", expectedContent, result.getResponse().getContentAsString());
151                        }
152                };
153        }
154
155        /**
156         * Assert the response body content as a byte array.
157         */
158        public ResultMatcher bytes(final byte[] expectedContent) {
159                return new ResultMatcher() {
160                        @Override
161                        public void match(MvcResult result) throws Exception {
162                                assertEquals("Response content", expectedContent, result.getResponse().getContentAsByteArray());
163                        }
164                };
165        }
166
167        /**
168         * Parse the response content and the given string as XML and assert the two
169         * are "similar" - i.e. they contain the same elements and attributes
170         * regardless of order.
171         * <p>Use of this matcher requires the <a
172         * href="http://xmlunit.sourceforge.net/">XMLUnit<a/> library.
173         * @param xmlContent the expected XML content
174         * @see MockMvcResultMatchers#xpath(String, Object...)
175         * @see MockMvcResultMatchers#xpath(String, Map, Object...)
176         */
177        public ResultMatcher xml(final String xmlContent) {
178                return new ResultMatcher() {
179                        @Override
180                        public void match(MvcResult result) throws Exception {
181                                String content = result.getResponse().getContentAsString();
182                                xmlHelper.assertXmlEqual(xmlContent, content);
183                        }
184                };
185        }
186
187        /**
188         * Parse the response content as {@link Node} and apply the given Hamcrest
189         * {@link Matcher}.
190         */
191        public ResultMatcher node(final Matcher<? super Node> matcher) {
192                return new ResultMatcher() {
193                        @Override
194                        public void match(MvcResult result) throws Exception {
195                                String content = result.getResponse().getContentAsString();
196                                xmlHelper.assertNode(content, matcher);
197                        }
198                };
199        }
200
201        /**
202         * Parse the response content as {@link DOMSource} and apply the given
203         * Hamcrest {@link Matcher}.
204         * @see <a href="https://code.google.com/p/xml-matchers/">xml-matchers</a>
205         */
206        public ResultMatcher source(final Matcher<? super Source> matcher) {
207                return new ResultMatcher() {
208                        @Override
209                        public void match(MvcResult result) throws Exception {
210                                String content = result.getResponse().getContentAsString();
211                                xmlHelper.assertSource(content, matcher);
212                        }
213                };
214        }
215
216        /**
217         * Parse the expected and actual strings as JSON and assert the two
218         * are "similar" - i.e. they contain the same attribute-value pairs
219         * regardless of formatting with a lenient checking (extensible, and non-strict array
220         * ordering).
221         * @param jsonContent the expected JSON content
222         * @since 4.1
223         */
224        public ResultMatcher json(final String jsonContent) {
225                return json(jsonContent, false);
226        }
227
228        /**
229         * Parse the response content and the given string as JSON and assert the two are "similar" -
230         * i.e. they contain the same attribute-value pairs regardless of formatting.
231         * <p>Can compare in two modes, depending on {@code strict} parameter value:
232         * <ul>
233         * <li>{@code true}: strict checking. Not extensible, and strict array ordering.</li>
234         * <li>{@code false}: lenient checking. Extensible, and non-strict array ordering.</li>
235         * </ul>
236         * <p>Use of this matcher requires the <a
237         * href="https://jsonassert.skyscreamer.org/">JSONassert<a/> library.
238         * @param jsonContent the expected JSON content
239         * @param strict enables strict checking
240         * @since 4.2
241         */
242        public ResultMatcher json(final String jsonContent, final boolean strict) {
243                return new ResultMatcher() {
244                        @Override
245                        public void match(MvcResult result) throws Exception {
246                                String content = result.getResponse().getContentAsString();
247                                jsonHelper.assertJsonEqual(jsonContent, content, strict);
248                        }
249                };
250        }
251
252}