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.client.match; 018 019import java.io.ByteArrayInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022 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.HttpHeaders; 030import org.springframework.http.HttpInputMessage; 031import org.springframework.http.MediaType; 032import org.springframework.http.client.ClientHttpRequest; 033import org.springframework.http.converter.FormHttpMessageConverter; 034import org.springframework.mock.http.client.MockClientHttpRequest; 035import org.springframework.test.util.JsonExpectationsHelper; 036import org.springframework.test.util.XmlExpectationsHelper; 037import org.springframework.test.web.client.RequestMatcher; 038import org.springframework.util.MultiValueMap; 039 040import static org.hamcrest.MatcherAssert.assertThat; 041import static org.springframework.test.util.AssertionErrors.assertEquals; 042import static org.springframework.test.util.AssertionErrors.assertTrue; 043 044/** 045 * Factory for request content {@code RequestMatcher}'s. An instance of this 046 * class is typically accessed via {@link MockRestRequestMatchers#content()}. 047 * 048 * @author Rossen Stoyanchev 049 * @since 3.2 050 */ 051public class ContentRequestMatchers { 052 053 private final XmlExpectationsHelper xmlHelper; 054 055 private final JsonExpectationsHelper jsonHelper; 056 057 058 /** 059 * Class constructor, not for direct instantiation. 060 * Use {@link MockRestRequestMatchers#content()}. 061 */ 062 protected ContentRequestMatchers() { 063 this.xmlHelper = new XmlExpectationsHelper(); 064 this.jsonHelper = new JsonExpectationsHelper(); 065 } 066 067 068 /** 069 * Assert the request content type as a String. 070 */ 071 public RequestMatcher contentType(String expectedContentType) { 072 return contentType(MediaType.parseMediaType(expectedContentType)); 073 } 074 075 /** 076 * Assert the request content type as a {@link MediaType}. 077 */ 078 public RequestMatcher contentType(MediaType expectedContentType) { 079 return request -> { 080 MediaType actualContentType = request.getHeaders().getContentType(); 081 assertTrue("Content type not set", actualContentType != null); 082 assertEquals("Content type", expectedContentType, actualContentType); 083 }; 084 } 085 086 /** 087 * Assert the request content type is compatible with the given 088 * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}. 089 */ 090 public RequestMatcher contentTypeCompatibleWith(String contentType) { 091 return contentTypeCompatibleWith(MediaType.parseMediaType(contentType)); 092 } 093 094 /** 095 * Assert the request content type is compatible with the given 096 * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}. 097 */ 098 public RequestMatcher contentTypeCompatibleWith(MediaType contentType) { 099 return request -> { 100 MediaType actualContentType = request.getHeaders().getContentType(); 101 assertTrue("Content type not set", actualContentType != null); 102 if (actualContentType != null) { 103 assertTrue("Content type [" + actualContentType + "] is not compatible with [" + contentType + "]", 104 actualContentType.isCompatibleWith(contentType)); 105 } 106 }; 107 } 108 109 /** 110 * Get the body of the request as a UTF-8 string and apply the given {@link Matcher}. 111 */ 112 public RequestMatcher string(Matcher<? super String> matcher) { 113 return request -> { 114 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 115 assertThat("Request content", mockRequest.getBodyAsString(), matcher); 116 }; 117 } 118 119 /** 120 * Get the body of the request as a UTF-8 string and compare it to the given String. 121 */ 122 public RequestMatcher string(String expectedContent) { 123 return request -> { 124 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 125 assertEquals("Request content", expectedContent, mockRequest.getBodyAsString()); 126 }; 127 } 128 129 /** 130 * Compare the body of the request to the given byte array. 131 */ 132 public RequestMatcher bytes(byte[] expectedContent) { 133 return request -> { 134 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 135 assertEquals("Request content", expectedContent, mockRequest.getBodyAsBytes()); 136 }; 137 } 138 139 /** 140 * Parse the body as form data and compare to the given {@code MultiValueMap}. 141 * @since 4.3 142 */ 143 public RequestMatcher formData(MultiValueMap<String, String> expectedContent) { 144 return request -> { 145 HttpInputMessage inputMessage = new HttpInputMessage() { 146 @Override 147 public InputStream getBody() throws IOException { 148 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 149 return new ByteArrayInputStream(mockRequest.getBodyAsBytes()); 150 } 151 @Override 152 public HttpHeaders getHeaders() { 153 return request.getHeaders(); 154 } 155 }; 156 FormHttpMessageConverter converter = new FormHttpMessageConverter(); 157 assertEquals("Request content", expectedContent, converter.read(null, inputMessage)); 158 }; 159 } 160 161 /** 162 * Parse the request body and the given String as XML and assert that the 163 * two are "similar" - i.e. they contain the same elements and attributes 164 * regardless of order. 165 * <p>Use of this matcher assumes the 166 * <a href="http://xmlunit.sourceforge.net/">XMLUnit</a> library is available. 167 * @param expectedXmlContent the expected XML content 168 */ 169 public RequestMatcher xml(String expectedXmlContent) { 170 return new AbstractXmlRequestMatcher() { 171 @Override 172 protected void matchInternal(MockClientHttpRequest request) throws Exception { 173 xmlHelper.assertXmlEqual(expectedXmlContent, request.getBodyAsString()); 174 } 175 }; 176 } 177 178 /** 179 * Parse the request content as {@link Node} and apply the given {@link Matcher}. 180 */ 181 public RequestMatcher node(Matcher<? super Node> matcher) { 182 return new AbstractXmlRequestMatcher() { 183 @Override 184 protected void matchInternal(MockClientHttpRequest request) throws Exception { 185 xmlHelper.assertNode(request.getBodyAsString(), matcher); 186 } 187 }; 188 } 189 190 /** 191 * Parse the request content as {@link DOMSource} and apply the given {@link Matcher}. 192 * @see <a href="https://code.google.com/p/xml-matchers/">https://code.google.com/p/xml-matchers/</a> 193 */ 194 public RequestMatcher source(Matcher<? super Source> matcher) { 195 return new AbstractXmlRequestMatcher() { 196 @Override 197 protected void matchInternal(MockClientHttpRequest request) throws Exception { 198 xmlHelper.assertSource(request.getBodyAsString(), matcher); 199 } 200 }; 201 } 202 203 /** 204 * Parse the expected and actual strings as JSON and assert the two 205 * are "similar" - i.e. they contain the same attribute-value pairs 206 * regardless of formatting with a lenient checking (extensible, and non-strict array 207 * ordering). 208 * <p>Use of this matcher requires the <a 209 * href="https://jsonassert.skyscreamer.org/">JSONassert</a> library. 210 * @param expectedJsonContent the expected JSON content 211 * @since 5.0.5 212 */ 213 public RequestMatcher json(String expectedJsonContent) { 214 return json(expectedJsonContent, false); 215 } 216 217 /** 218 * Parse the request body and the given string as JSON and assert the two 219 * are "similar" - i.e. they contain the same attribute-value pairs 220 * regardless of formatting. 221 * <p>Can compare in two modes, depending on {@code strict} parameter value: 222 * <ul> 223 * <li>{@code true}: strict checking. Not extensible, and strict array ordering.</li> 224 * <li>{@code false}: lenient checking. Extensible, and non-strict array ordering.</li> 225 * </ul> 226 * <p>Use of this matcher requires the <a 227 * href="https://jsonassert.skyscreamer.org/">JSONassert</a> library. 228 * @param expectedJsonContent the expected JSON content 229 * @param strict enables strict checking 230 * @since 5.0.5 231 */ 232 public RequestMatcher json(String expectedJsonContent, boolean strict) { 233 return request -> { 234 try { 235 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 236 this.jsonHelper.assertJsonEqual(expectedJsonContent, mockRequest.getBodyAsString(), strict); 237 } 238 catch (Exception ex) { 239 throw new AssertionError("Failed to parse expected or actual JSON request content", ex); 240 } 241 }; 242 } 243 244 245 /** 246 * Abstract base class for XML {@link RequestMatcher}'s. 247 */ 248 private abstract static class AbstractXmlRequestMatcher implements RequestMatcher { 249 250 @Override 251 public final void match(ClientHttpRequest request) throws IOException, AssertionError { 252 try { 253 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 254 matchInternal(mockRequest); 255 } 256 catch (Exception ex) { 257 throw new AssertionError("Failed to parse expected or actual XML request content", ex); 258 } 259 } 260 261 protected abstract void matchInternal(MockClientHttpRequest request) throws Exception; 262 } 263 264}