View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.test.util;
18  
19  import java.lang.reflect.Array;
20  import java.lang.reflect.Method;
21  import java.text.ParseException;
22  import java.util.List;
23  
24  import com.jayway.jsonpath.InvalidPathException;
25  import com.jayway.jsonpath.JsonPath;
26  import org.hamcrest.Matcher;
27  
28  import org.springframework.util.Assert;
29  import org.springframework.util.ReflectionUtils;
30  
31  import static org.springframework.test.util.AssertionErrors.*;
32  import static org.springframework.test.util.MatcherAssertionErrors.*;
33  
34  /**
35   * A helper class for applying assertions via JSON path expressions.
36   *
37   * <p>Based on the <a href="https://github.com/jayway/JsonPath">JsonPath</a>
38   * project: requiring version 0.9+, with 1.1+ strongly recommended.
39   *
40   * @author Rossen Stoyanchev
41   * @author Juergen Hoeller
42   * @since 3.2
43   */
44  public class JsonPathExpectationsHelper {
45  
46  	private static Method compileMethod;
47  
48  	private static Object emptyFilters;
49  
50  	static {
51  		// Reflective bridging between JsonPath 0.9.x and 1.x
52  		for (Method candidate : JsonPath.class.getMethods()) {
53  			if (candidate.getName().equals("compile")) {
54  				Class<?>[] paramTypes = candidate.getParameterTypes();
55  				if (paramTypes.length == 2 && paramTypes[0].equals(String.class) && paramTypes[1].isArray()) {
56  					compileMethod = candidate;
57  					emptyFilters = Array.newInstance(paramTypes[1].getComponentType(), 0);
58  					break;
59  				}
60  			}
61  		}
62  		Assert.state(compileMethod != null, "Unexpected JsonPath API - no compile(String, ...) method found");
63  	}
64  
65  
66  	private final String expression;
67  
68  	private final JsonPath jsonPath;
69  
70  
71  	/**
72  	 * Construct a new JsonPathExpectationsHelper.
73  	 * @param expression the JsonPath expression
74  	 * @param args arguments to parameterize the JSON path expression with
75  	 * formatting specifiers defined in {@link String#format(String, Object...)}
76  	 */
77  	public JsonPathExpectationsHelper(String expression, Object... args) {
78  		this.expression = String.format(expression, args);
79  		this.jsonPath = (JsonPath) ReflectionUtils.invokeMethod(
80  				compileMethod, null, this.expression, emptyFilters);
81  	}
82  
83  
84  	/**
85  	 * Evaluate the JSON path and assert the resulting value with the given {@code Matcher}.
86  	 * @param content the response content
87  	 * @param matcher the matcher to assert on the resulting json path
88  	 */
89  	@SuppressWarnings("unchecked")
90  	public <T> void assertValue(String content, Matcher<T> matcher) throws ParseException {
91  		T value = (T) evaluateJsonPath(content);
92  		assertThat("JSON path" + this.expression, value, matcher);
93  	}
94  
95  	private Object evaluateJsonPath(String content) throws ParseException  {
96  		String message = "No value for JSON path: " + this.expression + ", exception: ";
97  		try {
98  			return this.jsonPath.read(content);
99  		}
100 		catch (InvalidPathException ex) {
101 			throw new AssertionError(message + ex.getMessage());
102 		}
103 		catch (ArrayIndexOutOfBoundsException ex) {
104 			throw new AssertionError(message + ex.getMessage());
105 		}
106 		catch (IndexOutOfBoundsException ex) {
107 			throw new AssertionError(message + ex.getMessage());
108 		}
109 	}
110 
111 	/**
112 	 * Apply the JSON path and assert the resulting value.
113 	 */
114 	public void assertValue(String responseContent, Object expectedValue) throws ParseException {
115 		Object actualValue = evaluateJsonPath(responseContent);
116 		if ((actualValue instanceof List) && !(expectedValue instanceof List)) {
117 			@SuppressWarnings("rawtypes")
118 			List actualValueList = (List) actualValue;
119 			if (actualValueList.isEmpty()) {
120 				fail("No matching value for JSON path \"" + this.expression + "\"");
121 			}
122 			if (actualValueList.size() != 1) {
123 				fail("Got a list of values " + actualValue + " instead of the value " + expectedValue);
124 			}
125 			actualValue = actualValueList.get(0);
126 		}
127 		else if (actualValue != null && expectedValue != null) {
128 			assertEquals("For JSON path " + this.expression + " type of value",
129 					expectedValue.getClass(), actualValue.getClass());
130 		}
131 		assertEquals("JSON path " + this.expression, expectedValue, actualValue);
132 	}
133 
134 	/**
135 	 * Apply the JSON path and assert the resulting value is an array.
136 	 */
137 	public void assertValueIsArray(String responseContent) throws ParseException {
138 		Object actualValue = evaluateJsonPath(responseContent);
139 		assertTrue("No value for JSON path \"" + this.expression + "\"", actualValue != null);
140 		String reason = "Expected array at JSON path " + this.expression + " but found " + actualValue;
141 		assertTrue(reason, actualValue instanceof List);
142 	}
143 
144 	/**
145 	 * Evaluate the JSON path and assert the resulting content exists.
146 	 */
147 	public void exists(String content) throws ParseException {
148 		Object value = evaluateJsonPath(content);
149 		String reason = "No value for JSON path " + this.expression;
150 		assertTrue(reason, value != null);
151 		if (List.class.isInstance(value)) {
152 			assertTrue(reason, !((List<?>) value).isEmpty());
153 		}
154 	}
155 
156 	/**
157 	 * Evaluate the JSON path and assert it doesn't point to any content.
158 	 */
159 	public void doesNotExist(String content) throws ParseException {
160 		Object value;
161 		try {
162 			value = evaluateJsonPath(content);
163 		}
164 		catch (AssertionError ex) {
165 			return;
166 		}
167 		String reason = String.format("Expected no value for JSON path: %s but found: %s", this.expression, value);
168 		if (List.class.isInstance(value)) {
169 			assertTrue(reason, ((List<?>) value).isEmpty());
170 		}
171 		else {
172 			assertTrue(reason, value == null);
173 		}
174 	}
175 
176 }