001/*
002 * Copyright 2002-2019 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.nio.charset.StandardCharsets;
020import java.util.Map;
021
022import javax.servlet.http.HttpServletResponse;
023import javax.xml.transform.Source;
024import javax.xml.transform.dom.DOMSource;
025
026import org.hamcrest.Matcher;
027import org.w3c.dom.Node;
028
029import org.springframework.http.MediaType;
030import org.springframework.test.util.JsonExpectationsHelper;
031import org.springframework.test.util.XmlExpectationsHelper;
032import org.springframework.test.web.servlet.ResultMatcher;
033
034import static org.hamcrest.MatcherAssert.assertThat;
035import static org.springframework.test.util.AssertionErrors.assertEquals;
036import static org.springframework.test.util.AssertionErrors.assertNotNull;
037import static org.springframework.test.util.AssertionErrors.assertTrue;
038
039/**
040 * Factory for response content assertions.
041 *
042 * <p>An instance of this class is typically accessed via
043 * {@link MockMvcResultMatchers#content}.
044 *
045 * @author Rossen Stoyanchev
046 * @since 3.2
047 */
048public class ContentResultMatchers {
049
050        private final XmlExpectationsHelper xmlHelper;
051
052        private final JsonExpectationsHelper jsonHelper;
053
054
055        /**
056         * Protected constructor.
057         * Use {@link MockMvcResultMatchers#content()}.
058         */
059        protected ContentResultMatchers() {
060                this.xmlHelper = new XmlExpectationsHelper();
061                this.jsonHelper = new JsonExpectationsHelper();
062        }
063
064
065        /**
066         * Assert the ServletResponse content type. The given content type must
067         * fully match including type, sub-type, and parameters. For checking
068         * only the type and sub-type see {@link #contentTypeCompatibleWith(String)}.
069         */
070        public ResultMatcher contentType(String contentType) {
071                return contentType(MediaType.parseMediaType(contentType));
072        }
073
074        /**
075         * Assert the ServletResponse content type after parsing it as a MediaType.
076         * The given content type must fully match including type, sub-type, and
077         * parameters. For checking only the type and sub-type see
078         * {@link #contentTypeCompatibleWith(MediaType)}.
079         */
080        public ResultMatcher contentType(MediaType contentType) {
081                return result -> {
082                        String actual = result.getResponse().getContentType();
083                        assertNotNull("Content type not set", actual);
084                        assertEquals("Content type", contentType, MediaType.parseMediaType(actual));
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(MediaType contentType) {
101                return result -> {
102                        String actual = result.getResponse().getContentType();
103                        assertNotNull("Content type not set", actual);
104                        MediaType actualContentType = MediaType.parseMediaType(actual);
105                        assertTrue("Content type [" + actual + "] is not compatible with [" + contentType + "]",
106                                        actualContentType.isCompatibleWith(contentType));
107                };
108        }
109
110        /**
111         * Assert the character encoding in the ServletResponse.
112         * @see HttpServletResponse#getCharacterEncoding()
113         */
114        public ResultMatcher encoding(String characterEncoding) {
115                return result -> {
116                        String actual = result.getResponse().getCharacterEncoding();
117                        assertEquals("Character encoding", characterEncoding, actual);
118                };
119        }
120
121        /**
122         * Assert the response body content with a Hamcrest {@link Matcher}.
123         * <pre class="code">
124         * mockMvc.perform(get("/path"))
125         *   .andExpect(content().string(containsString("text")));
126         * </pre>
127         */
128        public ResultMatcher string(Matcher<? super String> matcher) {
129                return result -> assertThat("Response content", result.getResponse().getContentAsString(), matcher);
130        }
131
132        /**
133         * Assert the response body content as a String.
134         */
135        public ResultMatcher string(String expectedContent) {
136                return result -> assertEquals("Response content", expectedContent, result.getResponse().getContentAsString());
137        }
138
139        /**
140         * Assert the response body content as a byte array.
141         */
142        public ResultMatcher bytes(byte[] expectedContent) {
143                return result -> assertEquals("Response content", expectedContent, result.getResponse().getContentAsByteArray());
144        }
145
146        /**
147         * Parse the response content and the given string as XML and assert the two
148         * are "similar" - i.e. they contain the same elements and attributes
149         * regardless of order.
150         * <p>Use of this matcher requires the <a
151         * href="http://xmlunit.sourceforge.net/">XMLUnit</a> library.
152         * @param xmlContent the expected XML content
153         * @see MockMvcResultMatchers#xpath(String, Object...)
154         * @see MockMvcResultMatchers#xpath(String, Map, Object...)
155         */
156        public ResultMatcher xml(String xmlContent) {
157                return result -> {
158                        String content = result.getResponse().getContentAsString();
159                        this.xmlHelper.assertXmlEqual(xmlContent, content);
160                };
161        }
162
163        /**
164         * Parse the response content as {@link Node} and apply the given Hamcrest
165         * {@link Matcher}.
166         */
167        public ResultMatcher node(Matcher<? super Node> matcher) {
168                return result -> {
169                        String content = result.getResponse().getContentAsString();
170                        this.xmlHelper.assertNode(content, matcher);
171                };
172        }
173
174        /**
175         * Parse the response content as {@link DOMSource} and apply the given
176         * Hamcrest {@link Matcher}.
177         * @see <a href="https://code.google.com/p/xml-matchers/">xml-matchers</a>
178         */
179        public ResultMatcher source(Matcher<? super Source> matcher) {
180                return result -> {
181                        String content = result.getResponse().getContentAsString();
182                        this.xmlHelper.assertSource(content, matcher);
183                };
184        }
185
186        /**
187         * Parse the expected and actual strings as JSON and assert the two
188         * are "similar" - i.e. they contain the same attribute-value pairs
189         * regardless of formatting with a lenient checking (extensible, and non-strict array
190         * ordering).
191         * @param jsonContent the expected JSON content
192         * @since 4.1
193         */
194        public ResultMatcher json(String jsonContent) {
195                return json(jsonContent, false);
196        }
197
198        /**
199         * Parse the response content and the given string as JSON and assert the two are "similar" -
200         * i.e. they contain the same attribute-value pairs regardless of formatting.
201         * <p>Can compare in two modes, depending on {@code strict} parameter value:
202         * <ul>
203         * <li>{@code true}: strict checking. Not extensible, and strict array ordering.</li>
204         * <li>{@code false}: lenient checking. Extensible, and non-strict array ordering.</li>
205         * </ul>
206         * <p>Use of this matcher requires the <a
207         * href="https://jsonassert.skyscreamer.org/">JSONassert</a> library.
208         * @param jsonContent the expected JSON content
209         * @param strict enables strict checking
210         * @since 4.2
211         */
212        public ResultMatcher json(String jsonContent, boolean strict) {
213                return result -> {
214                        String content = result.getResponse().getContentAsString(StandardCharsets.UTF_8);
215                        this.jsonHelper.assertJsonEqual(jsonContent, content, strict);
216                };
217        }
218
219}