001/* 002 * Copyright 2012-2018 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 * http://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.boot.test.json; 018 019import java.io.BufferedReader; 020import java.io.Closeable; 021import java.io.File; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.InputStreamReader; 025import java.io.Reader; 026import java.io.StringReader; 027import java.lang.reflect.Field; 028 029import org.assertj.core.api.Assertions; 030 031import org.springframework.beans.factory.ObjectFactory; 032import org.springframework.core.ResolvableType; 033import org.springframework.core.io.ByteArrayResource; 034import org.springframework.core.io.ClassPathResource; 035import org.springframework.core.io.FileSystemResource; 036import org.springframework.core.io.InputStreamResource; 037import org.springframework.core.io.Resource; 038import org.springframework.util.Assert; 039import org.springframework.util.ReflectionUtils; 040 041/** 042 * Base class for AssertJ based JSON marshal testers. Exposes specific Asserts following a 043 * {@code read}, {@code write} or {@code parse} of JSON content. Typically used in 044 * combination with an AssertJ {@link Assertions#assertThat(Object) assertThat} call. For 045 * example: <pre class="code"> 046 * public class ExampleObjectJsonTests { 047 * 048 * private AbstractJsonTester<ExampleObject> json = //... 049 * 050 * @Test 051 * public void testWriteJson() { 052 * ExampleObject object = //... 053 * assertThat(json.write(object)).isEqualToJson("expected.json"); 054 * assertThat(json.read("expected.json")).isEqualTo(object); 055 * } 056 * 057 * } 058 * </pre> For a complete list of supported assertions see {@link JsonContentAssert} and 059 * {@link ObjectContentAssert}. 060 * <p> 061 * To use this library JSONAssert must be on the test classpath. 062 * 063 * @param <T> the type under test 064 * @author Phillip Webb 065 * @since 1.4.0 066 * @see JsonContentAssert 067 * @see ObjectContentAssert 068 */ 069public abstract class AbstractJsonMarshalTester<T> { 070 071 private Class<?> resourceLoadClass; 072 073 private ResolvableType type; 074 075 /** 076 * Create a new uninitialized {@link AbstractJsonMarshalTester} instance. 077 */ 078 protected AbstractJsonMarshalTester() { 079 } 080 081 /** 082 * Create a new {@link AbstractJsonMarshalTester} instance. 083 * @param resourceLoadClass the source class used when loading relative classpath 084 * resources 085 * @param type the type under test 086 */ 087 public AbstractJsonMarshalTester(Class<?> resourceLoadClass, ResolvableType type) { 088 Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null"); 089 Assert.notNull(type, "Type must not be null"); 090 initialize(resourceLoadClass, type); 091 } 092 093 /** 094 * Initialize the marshal tester for use. 095 * @param resourceLoadClass the source class used when loading relative classpath 096 * resources 097 * @param type the type under test 098 */ 099 protected final void initialize(Class<?> resourceLoadClass, ResolvableType type) { 100 if (this.resourceLoadClass == null && this.type == null) { 101 this.resourceLoadClass = resourceLoadClass; 102 this.type = type; 103 } 104 } 105 106 /** 107 * Return the type under test. 108 * @return the type under test 109 */ 110 protected final ResolvableType getType() { 111 return this.type; 112 } 113 114 /** 115 * Return class used to load relative resources. 116 * @return the resource load class 117 */ 118 protected final Class<?> getResourceLoadClass() { 119 return this.resourceLoadClass; 120 } 121 122 /** 123 * Return {@link JsonContent} from writing the specific value. 124 * @param value the value to write 125 * @return the {@link JsonContent} 126 * @throws IOException on write error 127 */ 128 public JsonContent<T> write(T value) throws IOException { 129 verify(); 130 Assert.notNull(value, "Value must not be null"); 131 String json = writeObject(value, this.type); 132 return new JsonContent<>(this.resourceLoadClass, this.type, json); 133 } 134 135 /** 136 * Return the object created from parsing the specific JSON bytes. 137 * @param jsonBytes the source JSON bytes 138 * @return the resulting object 139 * @throws IOException on parse error 140 */ 141 public T parseObject(byte[] jsonBytes) throws IOException { 142 verify(); 143 return parse(jsonBytes).getObject(); 144 } 145 146 /** 147 * Return {@link ObjectContent} from parsing the specific JSON bytes. 148 * @param jsonBytes the source JSON bytes 149 * @return the {@link ObjectContent} 150 * @throws IOException on parse error 151 */ 152 public ObjectContent<T> parse(byte[] jsonBytes) throws IOException { 153 verify(); 154 Assert.notNull(jsonBytes, "JsonBytes must not be null"); 155 return read(new ByteArrayResource(jsonBytes)); 156 } 157 158 /** 159 * Return the object created from parsing the specific JSON String. 160 * @param jsonString the source JSON string 161 * @return the resulting object 162 * @throws IOException on parse error 163 */ 164 public T parseObject(String jsonString) throws IOException { 165 verify(); 166 return parse(jsonString).getObject(); 167 } 168 169 /** 170 * Return {@link ObjectContent} from parsing the specific JSON String. 171 * @param jsonString the source JSON string 172 * @return the {@link ObjectContent} 173 * @throws IOException on parse error 174 */ 175 public ObjectContent<T> parse(String jsonString) throws IOException { 176 verify(); 177 Assert.notNull(jsonString, "JsonString must not be null"); 178 return read(new StringReader(jsonString)); 179 } 180 181 /** 182 * Return the object created from reading from the specified classpath resource. 183 * @param resourcePath the source resource path. May be a full path or a path relative 184 * to the {@code resourceLoadClass} passed to the constructor 185 * @return the resulting object 186 * @throws IOException on read error 187 */ 188 public T readObject(String resourcePath) throws IOException { 189 verify(); 190 return read(resourcePath).getObject(); 191 } 192 193 /** 194 * Return {@link ObjectContent} from reading from the specified classpath resource. 195 * @param resourcePath the source resource path. May be a full path or a path relative 196 * to the {@code resourceLoadClass} passed to the constructor 197 * @return the {@link ObjectContent} 198 * @throws IOException on read error 199 */ 200 public ObjectContent<T> read(String resourcePath) throws IOException { 201 verify(); 202 Assert.notNull(resourcePath, "ResourcePath must not be null"); 203 return read(new ClassPathResource(resourcePath, this.resourceLoadClass)); 204 } 205 206 /** 207 * Return the object created from reading from the specified file. 208 * @param file the source file 209 * @return the resulting object 210 * @throws IOException on read error 211 */ 212 public T readObject(File file) throws IOException { 213 verify(); 214 return read(file).getObject(); 215 } 216 217 /** 218 * Return {@link ObjectContent} from reading from the specified file. 219 * @param file the source file 220 * @return the {@link ObjectContent} 221 * @throws IOException on read error 222 */ 223 public ObjectContent<T> read(File file) throws IOException { 224 verify(); 225 Assert.notNull(file, "File must not be null"); 226 return read(new FileSystemResource(file)); 227 } 228 229 /** 230 * Return the object created from reading from the specified input stream. 231 * @param inputStream the source input stream 232 * @return the resulting object 233 * @throws IOException on read error 234 */ 235 public T readObject(InputStream inputStream) throws IOException { 236 verify(); 237 return read(inputStream).getObject(); 238 } 239 240 /** 241 * Return {@link ObjectContent} from reading from the specified input stream. 242 * @param inputStream the source input stream 243 * @return the {@link ObjectContent} 244 * @throws IOException on read error 245 */ 246 public ObjectContent<T> read(InputStream inputStream) throws IOException { 247 verify(); 248 Assert.notNull(inputStream, "InputStream must not be null"); 249 return read(new InputStreamResource(inputStream)); 250 } 251 252 /** 253 * Return the object created from reading from the specified resource. 254 * @param resource the source resource 255 * @return the resulting object 256 * @throws IOException on read error 257 */ 258 public T readObject(Resource resource) throws IOException { 259 verify(); 260 return read(resource).getObject(); 261 } 262 263 /** 264 * Return {@link ObjectContent} from reading from the specified resource. 265 * @param resource the source resource 266 * @return the {@link ObjectContent} 267 * @throws IOException on read error 268 */ 269 public ObjectContent<T> read(Resource resource) throws IOException { 270 verify(); 271 Assert.notNull(resource, "Resource must not be null"); 272 InputStream inputStream = resource.getInputStream(); 273 T object = readObject(inputStream, this.type); 274 closeQuietly(inputStream); 275 return new ObjectContent<>(this.type, object); 276 } 277 278 /** 279 * Return the object created from reading from the specified reader. 280 * @param reader the source reader 281 * @return the resulting object 282 * @throws IOException on read error 283 */ 284 public T readObject(Reader reader) throws IOException { 285 verify(); 286 return read(reader).getObject(); 287 } 288 289 /** 290 * Return {@link ObjectContent} from reading from the specified reader. 291 * @param reader the source reader 292 * @return the {@link ObjectContent} 293 * @throws IOException on read error 294 */ 295 public ObjectContent<T> read(Reader reader) throws IOException { 296 verify(); 297 Assert.notNull(reader, "Reader must not be null"); 298 T object = readObject(reader, this.type); 299 closeQuietly(reader); 300 return new ObjectContent<>(this.type, object); 301 } 302 303 private void closeQuietly(Closeable closeable) { 304 try { 305 closeable.close(); 306 } 307 catch (IOException ex) { 308 } 309 } 310 311 private void verify() { 312 Assert.state(this.resourceLoadClass != null, 313 "Uninitialized JsonMarshalTester (ResourceLoadClass is null)"); 314 Assert.state(this.type != null, "Uninitialized JsonMarshalTester (Type is null)"); 315 } 316 317 /** 318 * Write the specified object to a JSON string. 319 * @param value the source value (never {@code null}) 320 * @param type the resulting type (never {@code null}) 321 * @return the JSON string 322 * @throws IOException on write error 323 */ 324 protected abstract String writeObject(T value, ResolvableType type) 325 throws IOException; 326 327 /** 328 * Read from the specified input stream to create an object of the specified type. The 329 * default implementation delegates to {@link #readObject(Reader, ResolvableType)}. 330 * @param inputStream the source input stream (never {@code null}) 331 * @param type the resulting type (never {@code null}) 332 * @return the resulting object 333 * @throws IOException on read error 334 */ 335 protected T readObject(InputStream inputStream, ResolvableType type) 336 throws IOException { 337 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 338 return readObject(reader, type); 339 } 340 341 /** 342 * Read from the specified reader to create an object of the specified type. 343 * @param reader the source reader (never {@code null}) 344 * @param type the resulting type (never {@code null}) 345 * @return the resulting object 346 * @throws IOException on read error 347 */ 348 protected abstract T readObject(Reader reader, ResolvableType type) 349 throws IOException; 350 351 /** 352 * Utility class used to support field initialization. Used by subclasses to support 353 * {@code initFields}. 354 * 355 * @param <M> the marshaller type 356 */ 357 protected abstract static class FieldInitializer<M> { 358 359 private final Class<?> testerClass; 360 361 @SuppressWarnings("rawtypes") 362 protected FieldInitializer( 363 Class<? extends AbstractJsonMarshalTester> testerClass) { 364 Assert.notNull(testerClass, "TesterClass must not be null"); 365 this.testerClass = testerClass; 366 } 367 368 public void initFields(Object testInstance, M marshaller) { 369 Assert.notNull(testInstance, "TestInstance must not be null"); 370 Assert.notNull(marshaller, "Marshaller must not be null"); 371 initFields(testInstance, () -> marshaller); 372 } 373 374 public void initFields(Object testInstance, final ObjectFactory<M> marshaller) { 375 Assert.notNull(testInstance, "TestInstance must not be null"); 376 Assert.notNull(marshaller, "Marshaller must not be null"); 377 ReflectionUtils.doWithFields(testInstance.getClass(), 378 (field) -> doWithField(field, testInstance, marshaller)); 379 } 380 381 protected void doWithField(Field field, Object test, 382 ObjectFactory<M> marshaller) { 383 if (this.testerClass.isAssignableFrom(field.getType())) { 384 ReflectionUtils.makeAccessible(field); 385 Object existingValue = ReflectionUtils.getField(field, test); 386 if (existingValue == null) { 387 setupField(field, test, marshaller); 388 } 389 } 390 } 391 392 private void setupField(Field field, Object test, ObjectFactory<M> marshaller) { 393 ResolvableType type = ResolvableType.forField(field).getGeneric(); 394 ReflectionUtils.setField(field, test, 395 createTester(test.getClass(), type, marshaller.getObject())); 396 } 397 398 protected abstract AbstractJsonMarshalTester<Object> createTester( 399 Class<?> resourceLoadClass, ResolvableType type, M marshaller); 400 401 } 402 403}