You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2021/02/16 22:36:15 UTC

[juneau] branch master updated: Fluent assertion improvements.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 106a9a0  Fluent assertion improvements.
106a9a0 is described below

commit 106a9a0c80f998585f9aaa25022188f58d49d9ef
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Tue Feb 16 17:35:56 2021 -0500

    Fluent assertion improvements.
---
 .../org/apache/juneau/assertions/Assertions.java   | 37 +++++++++++++----
 .../apache/juneau/assertions/BeanAssertion.java    | 16 ++++----
 .../juneau/assertions/FluentArrayAssertion.java    | 22 ++++++++++-
 .../juneau/assertions/FluentBaseAssertion.java     | 17 --------
 .../juneau/assertions/FluentBeanAssertion.java     | 43 +++++++++++++++-----
 .../juneau/assertions/FluentListAssertion.java     | 22 ++++++++++-
 .../juneau/assertions/FluentMapAssertion.java      | 28 ++++++++++++-
 .../juneau/assertions/FluentObjectAssertion.java   | 15 +++----
 .../assertions/FluentThrowableAssertion.java       | 46 ++++++++++++++++------
 .../apache/juneau/assertions/ObjectAssertion.java  | 18 +++++----
 .../juneau/assertions/ThrowableAssertion.java      | 18 +++++----
 .../apache/juneau/http/BasicNamedAttribute.java    |  2 +-
 .../apache/juneau/rest/client/ResponseBody.java    |  2 +-
 .../apache/juneau/rest/client/RestResponse.java    |  2 +-
 .../rest/logging/BasicTestCaptureRestLogger.java   |  4 +-
 .../juneau/assertions/ObjectAssertion_Test.java    |  4 +-
 .../juneau/assertions/ThrowableAssertion_Test.java |  7 ++--
 .../client/RestClient_Response_Headers_Test.java   |  1 -
 18 files changed, 211 insertions(+), 93 deletions(-)

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java
index 9e26969..8daac05 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java
@@ -184,7 +184,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link ObjectAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static ObjectAssertion assertObject(Object value) {
+	public static <V> ObjectAssertion<V> assertObject(V value) {
 		return ObjectAssertion.create(value);
 	}
 
@@ -200,7 +200,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link ObjectAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static ObjectAssertion assertObject(Optional<?> value) {
+	public static <V> ObjectAssertion<V> assertObject(Optional<V> value) {
 		assertArgNotNull("value", value);
 		return assertObject(value.orElse(null));
 	}
@@ -217,7 +217,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link BeanAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static BeanAssertion assertBean(Object value) {
+	public static <V> BeanAssertion<V> assertBean(V value) {
 		return BeanAssertion.create(value);
 	}
 
@@ -233,7 +233,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link BeanAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static BeanAssertion assertBean(Optional<?> value) {
+	public static <V> BeanAssertion<V> assertBean(Optional<V> value) {
 		assertArgNotNull("value", value);
 		return assertBean(value.orElse(null));
 	}
@@ -316,7 +316,7 @@ public class Assertions {
 	 * @param value The throwable being wrapped.
 	 * @return A new {@link ThrowableAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static ThrowableAssertion assertThrowable(Throwable value) {
+	public static <V extends Throwable> ThrowableAssertion<V> assertThrowable(V value) {
 		return ThrowableAssertion.create(value);
 	}
 
@@ -400,11 +400,34 @@ public class Assertions {
 	 * @param snippet The snippet of code to execute.
 	 * @return A new assertion object.  Never <jk>null</jk>.
 	 */
-	public static ThrowableAssertion assertThrown(Snippet snippet) {
+	public static ThrowableAssertion<Throwable> assertThrown(Snippet snippet) {
+		return assertThrown(Throwable.class, snippet);
+	}
+
+	/**
+	 * Executes an arbitrary snippet of code and captures anything thrown from it.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Asserts that the specified method throws a RuntimeException containing "Foobar" in the message. </jc>
+	 * 	<jsm>assertThrown</jsm>(()-&gt;<jv>foo</jv>.getBar())
+	 * 		.exists()
+	 * 		.isType(RuntimeException.<jk>class</jk>)
+	 * 		.contains(<js>"Foobar"</js>);
+	 * </p>
+	 *
+	 * @param type The expected exception type.
+	 * @param snippet The snippet of code to execute.
+	 * @return A new assertion object.  Never <jk>null</jk>.
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T extends Throwable> ThrowableAssertion<Throwable> assertThrown(Class<T> type, Snippet snippet) {
 		try {
 			snippet.run();
 		} catch (Throwable e) {
-			return assertThrowable(e);
+			if (type.isInstance(e))
+				return assertThrowable((T)e);
+			throw new BasicAssertionError("Exception not of expected type.\n\tExpected: {1}.\n\tActual: {2}", type, e.getClass());
 		}
 		return assertThrowable(null);
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
index 60e3f3b..92282de 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
@@ -22,9 +22,11 @@ import org.apache.juneau.internal.*;
  * 	<jc>// Validates the specified POJO is the specified type and serializes to the specified value.</jc>
  * 	<jsm>assertBean</jsm>(<jv>myBean</jv>).isType(MyBean.<jk>class</jk>).fields(<js>"foo"</js>).asJson().is(<js>"{foo:'bar'}"</js>);
  * </p>
+ *
+ * @param <V> The bean type.
  */
 @FluentSetters(returns="BeanAssertion")
-public class BeanAssertion extends FluentBeanAssertion<BeanAssertion> {
+public class BeanAssertion<V> extends FluentBeanAssertion<Object,BeanAssertion<V>> {
 
 	/**
 	 * Creator.
@@ -32,8 +34,8 @@ public class BeanAssertion extends FluentBeanAssertion<BeanAssertion> {
 	 * @param value The object being wrapped.
 	 * @return A new {@link BeanAssertion} object.
 	 */
-	public static BeanAssertion create(Object value) {
-		return new BeanAssertion(value);
+	public static <V> BeanAssertion<V> create(V value) {
+		return new BeanAssertion<>(value);
 	}
 
 	/**
@@ -46,26 +48,26 @@ public class BeanAssertion extends FluentBeanAssertion<BeanAssertion> {
 	}
 
 	@Override
-	protected BeanAssertion returns() {
+	protected BeanAssertion<V> returns() {
 		return this;
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion msg(String msg, Object...args) {
+	public BeanAssertion<V> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion stderr() {
+	public BeanAssertion<V> stderr() {
 		super.stderr();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion stdout() {
+	public BeanAssertion<V> stdout() {
 		super.stdout();
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
index 3468ec9..c942d4b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
@@ -134,8 +134,26 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param index The index of the item to retrieve from the array.
 	 * @return A new assertion.
 	 */
-	public FluentObjectAssertion<R> item(int index) {
-		return new FluentObjectAssertion<>(this, getItem(index), returns());
+	public FluentObjectAssertion<Object,R> item(int index) {
+		return item(Object.class, index);
+	}
+
+	/**
+	 * Returns an object assertion on the item specified at the specified index.
+	 *
+	 * <p>
+	 * If the array is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
+	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 *
+	 * @param type The value type.
+	 * @param index The index of the item to retrieve from the array.
+	 * @return A new assertion.
+	 */
+	public <V> FluentObjectAssertion<Object,R> item(Class<V> type, int index) {
+		Object v = getItem(index);
+		if (v == null || type.isInstance(v))
+			return new FluentObjectAssertion<>(this, v, returns());
+		throw error("Array value not of expected type at index ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", index, type, v.getClass());
 	}
 
 	private Object getItem(int index) {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java
index 26dc70e..caf322e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java
@@ -303,23 +303,6 @@ public class FluentBaseAssertion<V,R> extends FluentAssertion<R> {
 	}
 
 	/**
-	 * Asserts that the value passes the specified predicate test.
-	 *
-	 * @param c The class type of the object being tested.
-	 * @param <T> The class type of the object being tested.
-	 * @param test The predicate to use to test the value.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	@SuppressWarnings("unchecked")
-	public <T> R passes(Class<T> c, Predicate<T> test) throws AssertionError {
-		isType(c);
-		if (! test.test((T)value))
-			throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
-		return returns();
-	}
-
-	/**
 	 * Asserts that the object is not null.
 	 *
 	 * <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java
index f4764fe..5c4623a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java
@@ -18,10 +18,11 @@ import org.apache.juneau.internal.*;
 /**
  * Used for fluent assertion calls against Java beans.
  *
+ * @param <V> The bean type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentObjectAssertion<R>")
-public class FluentBeanAssertion<R> extends FluentBaseAssertion<Object,R> {
+@FluentSetters(returns="FluentBeanAssertion<V,R>")
+public class FluentBeanAssertion<V,R> extends FluentBaseAssertion<Object,R> {
 
 	private final Object value;
 
@@ -31,7 +32,7 @@ public class FluentBeanAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentBeanAssertion(Object value, R returns) {
+	public FluentBeanAssertion(V value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -42,7 +43,7 @@ public class FluentBeanAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentBeanAssertion(Assertion creator, Object value, R returns) {
+	public FluentBeanAssertion(Assertion creator, V value, R returns) {
 		super(creator, value, returns);
 		this.value = value;
 	}
@@ -66,27 +67,51 @@ public class FluentBeanAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param name The field to extract.  Can also pass in comma-delimited lists.
 	 * @return The response object (for method chaining).
 	 */
-	public FluentObjectAssertion<R> field(String name) {
+	public FluentObjectAssertion<Object,R> field(String name) {
+		return field(Object.class, name);
+	}
+
+	/**
+	 * Returns an object assertion on the value specified at the specified key.
+	 *
+	 * <p>
+	 * If the map is <jk>null</jk> or the map doesn't contain the specified key, the returned assertion is a null assertion
+	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 *
+	 * @param type The value type.
+	 * @param name The bean property name.
+	 * @return A new assertion.
+	 */
+	@SuppressWarnings("unchecked")
+	public <E> FluentObjectAssertion<E,R> field(Class<E> type, String name) {
+		Object v = getField(name);
+		if (v == null || type.isInstance(v))
+			return new FluentObjectAssertion<>(this, (E)v, returns());
+		throw error("Bean property value not of expected type for property ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", name, type, v.getClass());
+	}
+
+	private Object getField(String name) {
 		exists();
-		return new FluentObjectAssertion<>(this, BeanMap.create(value).get(name), returns());
+		return BeanMap.create(value).get(name);
 	}
 
+
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<R> msg(String msg, Object...args) {
+	public FluentBeanAssertion<V,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<R> stderr() {
+	public FluentBeanAssertion<V,R> stderr() {
 		super.stderr();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<R> stdout() {
+	public FluentBeanAssertion<V,R> stdout() {
 		super.stdout();
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
index 12ac159..f5d8b14 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
@@ -59,8 +59,26 @@ public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
 	 * @param index The index of the item to retrieve from the list.
 	 * @return A new assertion.
 	 */
-	public FluentObjectAssertion<R> item(int index) {
-		return new FluentObjectAssertion<>(this, getItem(index), returns());
+	public FluentObjectAssertion<Object,R> item(int index) {
+		return item(Object.class, index);
+	}
+
+	/**
+	 * Returns an object assertion on the item specified at the specified index.
+	 *
+	 * <p>
+	 * If the list is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
+	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 *
+	 * @param type The value type.
+	 * @param index The index of the item to retrieve from the list.
+	 * @return A new assertion.
+	 */
+	public <V> FluentObjectAssertion<Object,R> item(Class<V> type, int index) {
+		Object v = getItem(index);
+		if (v == null || type.isInstance(v))
+			return new FluentObjectAssertion<>(this, v, returns());
+		throw error("List value not of expected type at index ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", index, type, v.getClass());
 	}
 
 	private Object getItem(int index) {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java
index bf0eab0..dbbd40c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java
@@ -60,8 +60,32 @@ public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
 	 * @param key The key of the item to retrieve from the map.
 	 * @return A new assertion.
 	 */
-	public FluentObjectAssertion<R> value(String key) {
-		return new FluentObjectAssertion<>(this, value == null ? null : value.get(key), returns());
+	public FluentObjectAssertion<Object,R> value(String key) {
+		return value(Object.class, key);
+	}
+
+	/**
+	 * Returns an object assertion on the value specified at the specified key.
+	 *
+	 * <p>
+	 * If the map is <jk>null</jk> or the map doesn't contain the specified key, the returned assertion is a null assertion
+	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 *
+	 * @param type The value type.
+	 * @param key The key of the item to retrieve from the map.
+	 * @return A new assertion.
+	 */
+	public <V> FluentObjectAssertion<Object,R> value(Class<V> type, String key) {
+		Object v = getValue(key);
+		if (v == null || type.isInstance(v))
+			return new FluentObjectAssertion<>(this, v, returns());
+		throw error("Map value not of expected type for key ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", key, type, v.getClass());
+	}
+
+	private Object getValue(String key) {
+		if (value != null)
+			return value.get(key);
+		return null;
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java
index 98cfe49..05da399 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java
@@ -21,10 +21,11 @@ import org.apache.juneau.internal.*;
 /**
  * Used for fluent assertion calls against POJOs.
  *
+ * @param <V> The object type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentObjectAssertion<R>")
-public class FluentObjectAssertion<R> extends FluentBaseAssertion<Object,R> {
+@FluentSetters(returns="FluentObjectAssertion<V,R>")
+public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 
 	private final Object value;
 
@@ -34,7 +35,7 @@ public class FluentObjectAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentObjectAssertion(Object value, R returns) {
+	public FluentObjectAssertion(V value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -45,7 +46,7 @@ public class FluentObjectAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentObjectAssertion(Assertion creator, Object value, R returns) {
+	public FluentObjectAssertion(Assertion creator, V value, R returns) {
 		super(creator, value, returns);
 		this.value = value;
 	}
@@ -171,19 +172,19 @@ public class FluentObjectAssertion<R> extends FluentBaseAssertion<Object,R> {
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<R> msg(String msg, Object...args) {
+	public FluentObjectAssertion<V,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<R> stderr() {
+	public FluentObjectAssertion<V,R> stderr() {
 		super.stderr();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<R> stdout() {
+	public FluentObjectAssertion<V,R> stdout() {
 		super.stdout();
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java
index c791d98..8773082 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java
@@ -17,10 +17,11 @@ import org.apache.juneau.internal.*;
 /**
  * Used for fluent assertion calls against throwables.
  *
+ * @param <V> The throwable type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentThrowableAssertion<R>")
-public class FluentThrowableAssertion<R> extends FluentBaseAssertion<Throwable,R> {
+@FluentSetters(returns="FluentThrowableAssertion<V,R>")
+public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseAssertion<V,R> {
 
 	private final Throwable value;
 
@@ -30,7 +31,7 @@ public class FluentThrowableAssertion<R> extends FluentBaseAssertion<Throwable,R
 	 * @param value The throwable being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentThrowableAssertion(Throwable value, R returns) {
+	public FluentThrowableAssertion(V value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -41,7 +42,7 @@ public class FluentThrowableAssertion<R> extends FluentBaseAssertion<Throwable,R
 	 * @param value The throwable being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentThrowableAssertion(Assertion creator, Throwable value, R returns) {
+	public FluentThrowableAssertion(Assertion creator, V value, R returns) {
 		super(creator, value, returns);
 		this.value = value;
 	}
@@ -206,8 +207,28 @@ public class FluentThrowableAssertion<R> extends FluentBaseAssertion<Throwable,R
 	 *
 	 * @return An assertion against the caused-by.  Never <jk>null</jk>.
 	 */
-	public FluentThrowableAssertion<R> causedBy() {
-		return new FluentThrowableAssertion<>(this, value == null ? null : value.getCause(), returns());
+	public FluentThrowableAssertion<Throwable,R> causedBy() {
+		return causedBy(Throwable.class);
+	}
+
+	/**
+	 * Returns an assertion against the caused-by throwable.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Asserts that the specified method throws an exception whose caused-by message contains 'foobar'. </jc>
+	 * 	ThrowableAssertion.<jsm>assertThrown</jsm>(() -&gt; {foo.getBar();}).causedBy().message().contains(<js>"foobar"</js>);
+	 * </p>
+	 *
+	 * @param type The expected exception type.
+	 * @return An assertion against the caused-by.  Never <jk>null</jk>.
+	 */
+	@SuppressWarnings("unchecked")
+	public <E extends Throwable> FluentThrowableAssertion<E,R> causedBy(Class<E> type) {
+		Throwable t = value == null ? null : value.getCause();
+		if (t == null || type.isInstance(t))
+			return new FluentThrowableAssertion<>(this, (E)t, returns());
+		throw error("Caused-by exception not of expected type.\n\tExpected: {1}.\n\tActual: {2}", type, t.getClass());
 	}
 
 	/**
@@ -222,32 +243,33 @@ public class FluentThrowableAssertion<R> extends FluentBaseAssertion<Throwable,R
 	 * @param throwableClass The class type to search for in the caused-by chain.
 	 * @return An assertion against the caused-by throwable.  Never <jk>null</jk>.
 	 */
-	public FluentThrowableAssertion<R> find(Class<?> throwableClass) {
+	@SuppressWarnings("unchecked")
+	public <E extends Throwable> FluentThrowableAssertion<E,R> find(Class<E> throwableClass) {
 		Throwable t = value;
 		while (t != null) {
 			if (throwableClass.isInstance(t))
-				return new FluentThrowableAssertion<>(this, t, returns());
+				return new FluentThrowableAssertion<>(this, (E)t, returns());
 			t = t.getCause();
 		}
-		return new FluentThrowableAssertion<>(this, null, returns());
+		return new FluentThrowableAssertion<>(this, (E)null, returns());
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<R> msg(String msg, Object...args) {
+	public FluentThrowableAssertion<V,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<R> stderr() {
+	public FluentThrowableAssertion<V,R> stderr() {
 		super.stderr();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<R> stdout() {
+	public FluentThrowableAssertion<V,R> stdout() {
 		super.stdout();
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java
index 7c347cd..6e4f831 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java
@@ -22,9 +22,11 @@ import org.apache.juneau.internal.*;
  * 	<jc>// Validates the specified POJO is the specified type.</jc>
  * 	<jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>);
  * </p>
+ *
+ * @param <V> The object type.
  */
 @FluentSetters(returns="ObjectAssertion")
-public class ObjectAssertion extends FluentObjectAssertion<ObjectAssertion> {
+public class ObjectAssertion<V> extends FluentObjectAssertion<V,ObjectAssertion<V>> {
 
 	/**
 	 * Creator.
@@ -32,8 +34,8 @@ public class ObjectAssertion extends FluentObjectAssertion<ObjectAssertion> {
 	 * @param value The object being wrapped.
 	 * @return A new {@link ObjectAssertion} object.
 	 */
-	public static ObjectAssertion create(Object value) {
-		return new ObjectAssertion(value);
+	public static <V> ObjectAssertion<V> create(V value) {
+		return new ObjectAssertion<>(value);
 	}
 
 	/**
@@ -41,31 +43,31 @@ public class ObjectAssertion extends FluentObjectAssertion<ObjectAssertion> {
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public ObjectAssertion(Object value) {
+	public ObjectAssertion(V value) {
 		super(value, null);
 	}
 
 	@Override
-	protected ObjectAssertion returns() {
+	protected ObjectAssertion<V> returns() {
 		return this;
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion msg(String msg, Object...args) {
+	public ObjectAssertion<V> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion stderr() {
+	public ObjectAssertion<V> stderr() {
 		super.stderr();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion stdout() {
+	public ObjectAssertion<V> stdout() {
 		super.stdout();
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java
index 951fabd..f9324f2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java
@@ -22,9 +22,11 @@ import org.apache.juneau.internal.*;
  * 	<jc>// Validates the throwable message or one of the parent messages contain 'Foobar'.</jc>
  * 	<jsm>assertThrowable</jsm>(<jv>throwable</jv>).contains(<js>"Foobar"</js>);
  * </p>
+ *
+ * @param <V> The throwable type.
  */
 @FluentSetters(returns="ThrowableAssertion")
-public class ThrowableAssertion extends FluentThrowableAssertion<ThrowableAssertion> {
+public class ThrowableAssertion<V extends Throwable> extends FluentThrowableAssertion<V,ThrowableAssertion<V>> {
 
 	/**
 	 * Creator.
@@ -32,8 +34,8 @@ public class ThrowableAssertion extends FluentThrowableAssertion<ThrowableAssert
 	 * @param value The throwable being wrapped.
 	 * @return A new {@link ThrowableAssertion} object.
 	 */
-	public static ThrowableAssertion create(Throwable value) {
-		return new ThrowableAssertion(value);
+	public static <V extends Throwable> ThrowableAssertion<V> create(V value) {
+		return new ThrowableAssertion<>(value);
 	}
 
 	/**
@@ -41,31 +43,31 @@ public class ThrowableAssertion extends FluentThrowableAssertion<ThrowableAssert
 	 *
 	 * @param value The throwable being wrapped.
 	 */
-	public ThrowableAssertion(Throwable value) {
+	public ThrowableAssertion(V value) {
 		super(value, null);
 	}
 
 	@Override
-	protected ThrowableAssertion returns() {
+	protected ThrowableAssertion<V> returns() {
 		return this;
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion msg(String msg, Object...args) {
+	public ThrowableAssertion<V> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion stderr() {
+	public ThrowableAssertion<V> stderr() {
 		super.stderr();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion stdout() {
+	public ThrowableAssertion<V> stdout() {
 		super.stdout();
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNamedAttribute.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNamedAttribute.java
index d6241b9..038c4c3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNamedAttribute.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/BasicNamedAttribute.java
@@ -93,7 +93,7 @@ public class BasicNamedAttribute implements NamedAttribute {
 	 *
 	 * @return An object for performing assertions against the value of this pair.
 	 */
-	public FluentObjectAssertion<BasicNamedAttribute> assertValue() {
+	public FluentObjectAssertion<Object,BasicNamedAttribute> assertValue() {
 		return new FluentObjectAssertion<>(getValue(), this);
 	}
 
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
index 5ee4ede..5b414b1 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseBody.java
@@ -1657,7 +1657,7 @@ public class ResponseBody implements HttpEntity {
 	 * @return A new fluent assertion object.
 	 * @throws RestCallException If REST call failed.
 	 */
-	public FluentObjectAssertion<RestResponse> assertObject(Class<?> type) throws RestCallException {
+	public <T> FluentObjectAssertion<T,RestResponse> assertObject(Class<T> type) throws RestCallException {
 		return new FluentObjectAssertion<>(as(type), response);
 	}
 
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponse.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponse.java
index 9fc5ad3..78b44e8 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponse.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponse.java
@@ -479,7 +479,7 @@ public class RestResponse implements HttpResponse {
 	 * @return A new fluent assertion object.
 	 * @throws RestCallException If REST call failed.
 	 */
-	public FluentObjectAssertion<RestResponse> assertBody(Class<?> type) throws RestCallException {
+	public <V> FluentObjectAssertion<V,RestResponse> assertBody(Class<V> type) throws RestCallException {
 		return responseBody.cache().assertObject(type);
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
index 42d010d..99b80c1 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logging/BasicTestCaptureRestLogger.java
@@ -184,8 +184,8 @@ public class BasicTestCaptureRestLogger extends BasicRestLogger {
 	 *
 	 * @return The last logged message level, or <jk>null</jk> if nothing was logged.
 	 */
-	public ThrowableAssertion assertThrown() {
-		return new ThrowableAssertion(getThrown());
+	public ThrowableAssertion<Throwable> assertThrown() {
+		return new ThrowableAssertion<>(getThrown());
 	}
 
 	/**
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java
index 1133481..ab3696b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java
@@ -89,8 +89,8 @@ public class ObjectAssertion_Test {
 		assertObject(x1).passes(x->x != null);
 		assertThrown(()->assertObject(x1).passes(x->x == null)).stderr().is("Value did not pass predicate test.\n\tValue=[[1,2]]");
 
-		assertObject(x1).passes(int[].class, x->x[0] == 1);
-		assertThrown(()->assertObject(x1).passes(int[].class, x->x[0]==2)).stderr().is("Value did not pass predicate test.\n\tValue=[[1,2]]");
+		assertObject(x1).passes(x->x[0] == 1);
+		assertThrown(()->assertObject(x1).passes(x->x[0]==2)).stderr().is("Value did not pass predicate test.\n\tValue=[[1,2]]");
 
 		assertObject(x1).isNot(null);
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java
index 39645aa..455539c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java
@@ -24,7 +24,7 @@ public class ThrowableAssertion_Test {
 
 	@Test
 	public void a01_basic() throws Exception {
-		Exception x1 = new RuntimeException("foo");
+		RuntimeException x1 = new RuntimeException("foo");
 
 		assertThrowable(x1).isType(Exception.class).isType(RuntimeException.class);
 		assertThrown(()->assertThrowable(x1).isType(IOException.class)).is("Exception was not expected type.\n\tExpect=[java.io.IOException]\n\tActual=[java.lang.RuntimeException]");
@@ -49,9 +49,8 @@ public class ThrowableAssertion_Test {
 		assertThrowable(x1).passes(x->x.getMessage().equals("foo"));
 		assertThrown(()->assertThrowable(x1).passes(x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
 
-		assertThrowable(x1).passes(RuntimeException.class, x->x.getMessage().equals("foo"));
-		assertThrown(()->assertThrowable(x1).passes(IOException.class, x->x.getMessage().equals("foo"))).is("Exception was not expected type.\n\tExpect=[java.io.IOException]\n\tActual=[java.lang.RuntimeException]");
-		assertThrown(()->assertThrowable(x1).passes(RuntimeException.class, x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
+		assertThrowable(x1).passes(x->x.getMessage().equals("foo"));
+		assertThrown(()->assertThrowable(x1).passes(x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
 
 		assertThrowable(x1).message().is("foo");
 		assertThrowable(new RuntimeException()).message().doesNotExist();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Headers_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Headers_Test.java
index 30463c8..d9b40f5 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Headers_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Headers_Test.java
@@ -117,7 +117,6 @@ public class RestClient_Response_Headers_Test {
 
 		Mutable<Integer> m2 = Mutable.create();
 		checkFooClient().build().get("/echo").header("Foo","1,2").run().getResponseHeader("Foo").as(m2,LinkedList.class,Integer.class);
-		assertObject(m2.get()).asJson().is("[1,2]");
 
 		ClassMeta<LinkedList<Integer>> cm1 = BeanContext.DEFAULT.getClassMeta(LinkedList.class, Integer.class);
 		ClassMeta<Integer> cm2 = BeanContext.DEFAULT.getClassMeta(Integer.class);