001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.test; 021 022import com.google.gson.GsonBuilder; 023import com.google.gson.JsonElement; 024import com.google.gson.JsonParser; 025import java.io.IOException; 026import java.net.URL; 027import java.nio.charset.StandardCharsets; 028import org.apache.commons.io.IOUtils; 029import org.junit.ComparisonFailure; 030 031/** 032 * Assertion to compare JSON documents. Comparison is not strict: 033 * <ul> 034 * <li>formatting differences are ignored</li> 035 * <li>order of elements in objects <code>{}</code> is not verified</li> 036 * <li>objects can contain more elements than expected, for example <code>{"one":1, "two":2}</code> 037 * matches <code>{"one":1}</code></li> 038 * <li>order of elements in arrays <code>[]</code> is not verified by default, for example <code>[1, 2]</code> 039 * matches <code>[2, 1]</code>. This mode can be disabled with {@link #withStrictArrayOrder()}</li> 040 * <li>timezones in datetime values are not strictly verified, for example <code>{"foo": "2015-01-01T13:00:00+2000"}</code> 041 * matches <code>{"foo": "2015-01-01T10:00:00-1000"}</code>. This feature can be disabled with 042 * {@link #withStrictTimezone()} 043 * </li> 044 * </ul> 045 * 046 * <h3>Usage</h3> 047 * <pre> 048 * String actual = "{}"; 049 * String expected = "{}"; 050 * JsonAssert.assertJson(actual).isSimilarTo(expected); 051 * </pre> 052 * 053 * <p>Expected JSON document can be loaded from URLs:</p> 054 * <pre> 055 * String actual = "{}"; 056 * JsonAssert.assertJson(actual).isSimilarTo(getClass().getResource("MyTest/expected.json")); 057 * </pre> 058 * 059 * @since 5.2 060 */ 061public class JsonAssert { 062 063 private final String actualJson; 064 private final JsonComparison comparison = new JsonComparison(); 065 066 private JsonAssert(String actualJson) { 067 this.actualJson = actualJson; 068 } 069 070 public JsonAssert withStrictTimezone() { 071 comparison.withTimezone(); 072 return this; 073 } 074 075 public JsonAssert withStrictArrayOrder() { 076 comparison.withStrictArrayOrder(); 077 return this; 078 } 079 080 public JsonAssert ignoreFields(String... ignoredFields) { 081 comparison.setIgnoredFields(ignoredFields); 082 return this; 083 } 084 085 public JsonAssert isSimilarTo(String expected) { 086 boolean similar = comparison.areSimilar(expected, actualJson); 087 if (!similar) { 088 throw new ComparisonFailure("Not a super-set of expected JSON -", pretty(expected), pretty(actualJson)); 089 } 090 return this; 091 } 092 093 public JsonAssert isSimilarTo(URL expected) { 094 return isSimilarTo(urlToString(expected)); 095 } 096 097 public static JsonAssert assertJson(String actualJson) { 098 return new JsonAssert(actualJson); 099 } 100 101 public static JsonAssert assertJson(URL actualJson) { 102 return new JsonAssert(urlToString(actualJson)); 103 } 104 105 private static String urlToString(URL url) { 106 try { 107 return IOUtils.toString(url, StandardCharsets.UTF_8); 108 } catch (IOException e) { 109 throw new IllegalStateException("Fail to load JSON from " + url, e); 110 } 111 } 112 113 private static String pretty(String json) { 114 JsonElement gson = new JsonParser().parse(json); 115 return new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(gson); 116 } 117}