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/17 14:59:24 UTC

[juneau] branch master updated: REST refactoring

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 1eaf420  REST refactoring
1eaf420 is described below

commit 1eaf420b896f105e3d7c6eae6765fe24308823f9
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Wed Feb 17 09:56:56 2021 -0500

    REST refactoring
---
 .../org/apache/juneau/assertions/Assertion.java    |  16 +-
 .../juneau/assertions/FluentStringAssertion.java   |   2 +-
 .../apache/juneau/rest/client/ResponseBody.java    | 299 +-------------------
 .../apache/juneau/rest/client/ResponseHeader.java  | 179 +-----------
 .../org/apache/juneau/rest/client/RestClient.java  |  13 +-
 .../apache/juneau/rest/client/RestResponse.java    |  66 +----
 .../assertion/FluentResponseBodyAssertion.java     | 306 +++++++++++++++++++++
 .../rest/client/RestClient_BasicCalls_Test.java    |   2 +-
 .../juneau/rest/client/RestClient_Body_Test.java   |  26 +-
 .../client/RestClient_Config_BeanContext_Test.java |   4 +-
 .../client/RestClient_Config_OpenApi_Test.java     |   6 +-
 .../rest/client/RestClient_FormData_Test.java      |  34 +--
 .../juneau/rest/client/RestClient_Query_Test.java  |  24 +-
 .../rest/client/RestClient_Response_Body_Test.java |  44 +--
 .../client/RestClient_Response_Headers_Test.java   |  30 +-
 .../rest/client/RestClient_Response_Test.java      |  18 +-
 16 files changed, 425 insertions(+), 644 deletions(-)

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java
index a73ab24..46171f0 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java
@@ -86,14 +86,26 @@ public class Assertion {
 	 * @return A new {@link BasicAssertionError}.
 	 */
 	protected BasicAssertionError error(String msg, Object...args) {
+		return error(null, msg, args);
+	}
+
+	/**
+	 * Creates a new {@link BasicAssertionError}.
+	 *
+	 * @param cause Optional caused-by throwable.
+	 * @param msg The message.
+	 * @param args The message arguments.
+	 * @return A new {@link BasicAssertionError}.
+	 */
+	protected BasicAssertionError error(Throwable cause, String msg, Object...args) {
 		msg = format(msg, args);
 		if (this.msg != null)
-			msg = format(this.msg, this.msgArgs).replace("<<<MSG>>>", msg);
+			msg = format(this.msg, this.msgArgs).replace("<<<MSG>>>", msg).replace("<<<CAUSED-BY>>>", cause == null ? "" : "Caused by: " + cause.getMessage());
 		if (stdout)
 			System.out.println(msg);  // NOT DEBUG
 		if (stderr)
 			System.err.println(msg);  // NOT DEBUG
-		return new BasicAssertionError(msg);
+		return new BasicAssertionError(cause, msg);
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java
index 1438798..56ec3dd 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java
@@ -360,7 +360,7 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	}
 
 	/**
-	 * Asserts that the text is not empty.
+	 * Asserts that the text is empty.
 	 *
 	 * @return The response object (for method chaining).
 	 * @throws AssertionError If assertion failed.
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 5b414b1..fee02de 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
@@ -24,7 +24,6 @@ import java.util.regex.*;
 import org.apache.http.*;
 import org.apache.http.conn.*;
 import org.apache.juneau.*;
-import org.apache.juneau.assertions.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.httppart.*;
@@ -33,6 +32,7 @@ import org.apache.juneau.oapi.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.parser.ParseException;
 import org.apache.juneau.reflect.*;
+import org.apache.juneau.rest.client.assertion.*;
 import org.apache.juneau.utils.*;
 
 /**
@@ -908,32 +908,6 @@ public class ResponseBody implements HttpEntity {
 	}
 
 	/**
-	 * Same as {@link #as(Mutable,Class)} but allows you to run the call asynchronously.
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 *		If {@link #cache()} or {@link RestResponse#cacheBody()} has been called, this method can be can be called multiple times and/or combined with
-	 *		other methods that retrieve the content of the response.  Otherwise a {@link RestCallException}
-	 *		with an inner {@link IllegalStateException} will be thrown.
-	 * 	<li>
-	 * 		The input stream is automatically closed after the execution of the future.
-	 * </ul>
-	 *
-	 * @param <T> The class type of the object being created.
-	 * @param m The mutable to set the parsed value in.
-	 * @param type The object type to create.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If the executor service was not defined.
-	 * @see
-	 * 	RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating
-	 * 	{@link Future Futures}.
-	 */
-	public <T> RestResponse asFuture(Mutable<Future<T>> m, Class<T> type) throws RestCallException {
-		m.set(asFuture(type));
-		return response;
-	}
-
-	/**
 	 * Same as {@link #as(ClassMeta)} but allows you to run the call asynchronously.
 	 *
 	 * <ul class='notes'>
@@ -967,34 +941,6 @@ public class ResponseBody implements HttpEntity {
 	}
 
 	/**
-	 * Same as {@link #as(Mutable,ClassMeta)} but allows you to run the call asynchronously.
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 *		If {@link #cache()} or {@link RestResponse#cacheBody()} has been called, this method can be can be called multiple times and/or combined with
-	 *		other methods that retrieve the content of the response.  Otherwise a {@link RestCallException}
-	 *		with an inner {@link IllegalStateException} will be thrown.
-	 * 	<li>
-	 * 		The input stream is automatically closed after the execution of the future.
-	 * </ul>
-	 *
-	 * @param <T>
-	 * 	The class type of the object being created.
-	 * 	See {@link #as(Type, Type...)} for details.
-	 * @param m The mutable to set the parsed value in.
-	 * @param type The object type to create.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If the executor service was not defined.
-	 * @see
-	 * 	RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating
-	 * 	{@link Future Futures}.
-	 */
-	public <T> RestResponse asFuture(Mutable<Future<T>>m, ClassMeta<T> type) throws RestCallException {
-		m.set(asFuture(type));
-		return response;
-	}
-
-	/**
 	 * Same as {@link #as(Type,Type...)} but allows you to run the call asynchronously.
 	 *
 	 * <ul class='notes'>
@@ -1034,40 +980,6 @@ public class ResponseBody implements HttpEntity {
 	}
 
 	/**
-	 * Same as {@link #as(Mutable,Type,Type...)} but allows you to run the call asynchronously.
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 *		If {@link #cache()} or {@link RestResponse#cacheBody()} has been called, this method can be can be called multiple times and/or combined with
-	 *		other methods that retrieve the content of the response.  Otherwise a {@link RestCallException}
-	 *		with an inner {@link IllegalStateException} will be thrown.
-	 * 	<li>
-	 * 		The input stream is automatically closed after the execution of the future.
-	 * </ul>
-	 *
-	 * @param <T>
-	 * 	The class type of the object being created.
-	 * 	See {@link #as(Type, Type...)} for details.
-	 * @param m The mutable to set the parsed value in.
-	 * @param type
-	 * 	The object type to create.
-	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
-	 * @param args
-	 * 	The type arguments of the class if it's a collection or map.
-	 * 	<br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType}
-	 * 	<br>Ignored if the main type is not a map or collection.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If the executor service was not defined.
-	 * @see
-	 * 	RestClientBuilder#executorService(ExecutorService, boolean) for defining the executor service for creating
-	 * 	{@link Future Futures}.
-	 */
-	public <T> RestResponse asFuture(Mutable<Future<T>> m, Type type, Type...args) throws RestCallException {
-		m.set(asFuture(type, args));
-		return response;
-	}
-
-	/**
 	 * Returns the contents of this body as a string.
 	 *
 	 * <ul class='notes'>
@@ -1313,45 +1225,6 @@ public class ResponseBody implements HttpEntity {
 	}
 
 	/**
-	 * Same as {@link #asMatcher(Pattern)} but sets the value in a mutable for fluent calls.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Parse response using a regular expression.</jc>
-	 * 	Mutable&lt;Matcher&gt; <jv>mutable</jv> = Mutable.<jsm>create</jsm>();
-	 *
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getBody().asMatcher(<jv>mutable</jv>, Pattern.<jsm>compile</jsm>(<js>"foo=(.*)"</js>))
-	 * 		.assertStatus().is(200);
-	 *
-	 * 	<jk>if</jk> (<jv>matcher</jv>.get().matches()) {
-	 * 		String <jv>foo</jv> = <jv>matcher</jv>.get().group(1);
-	 * 	}
-	 * </p>
-	 *
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		This method automatically calls {@link #cache()} so that the body can be retrieved multiple times.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @param m The mutable to set the value in.
-	 * @param pattern The regular expression pattern to match.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If a connection error occurred.
-	 */
-	public RestResponse asMatcher(Mutable<Matcher> m, Pattern pattern) throws RestCallException {
-		m.set(pattern.matcher(asString()));
-		return response;
-	}
-
-	/**
 	 * Converts the contents of the response body to a string and then matches the specified pattern against it.
 	 *
 	 * <h5 class='section'>Example:</h5>
@@ -1388,46 +1261,6 @@ public class ResponseBody implements HttpEntity {
 	}
 
 	/**
-	 * Same as {@link #asMatcher(String)} but sets the value in a mutable for fluent calls.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Parse response using a regular expression.</jc>
-	 * 	Mutable&lt;Matcher&gt; <jv>mutable</jv> = Mutable.<jsm>create</jsm>();
-	 *
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getBody().asMatcher(<jv>mutable</jv>, <js>"foo=(.*)"</js>)
-	 * 		.assertStatus().is(200);
-	 *
-	 * 	<jk>if</jk> (<jv>mutable</jv>.get().matches()) {
-	 * 		String <jv>foo</jv> = <jv>mutable</jv>.get().group(1);
-	 * 	}
-	 *
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		If {@link #cache()} or {@link RestResponse#cacheBody()} has been called, this method can be can be called multiple times and/or combined with
-	 *		other methods that retrieve the content of the response.  Otherwise a {@link RestCallException}
-	 *		with an inner {@link IllegalStateException} will be thrown.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @param m The mutable to set the value in.
-	 * @param regex The regular expression pattern to match.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If a connection error occurred.
-	 */
-	public RestResponse asMatcher(Mutable<Matcher> m, String regex) throws RestCallException {
-		asMatcher(m, regex, 0);
-		return response;
-	}
-
-	/**
 	 * Converts the contents of the response body to a string and then matches the specified pattern against it.
 	 *
 	 * <h5 class='section'>Example:</h5>
@@ -1465,48 +1298,6 @@ public class ResponseBody implements HttpEntity {
 	}
 
 	/**
-	 * Same as {@link #asMatcher(String,int)} but sets the value in a mutable for fluent calls.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Parse response using a regular expression.</jc>
-	 * 	Mutable&lt;Matcher&gt; <jv>mutable</jv> = Mutable.<jsm>create</jsm>();
-	 *
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getBody().asMatcher(<jv>mutable</jv>, <js>"foo=(.*)"</js>, <jsf>MULTILINE</jsf> &amp; <jsf>CASE_INSENSITIVE</jsf>)
-	 * 		.assertStatus().is(200);
-	 *
-	 * 	<jk>if</jk> (<jv>mutable</jv>.get().matches()) {
-	 * 		String <jv>foo</jv> = <jv>mutable</jv>.get().group(1);
-	 * 	}
-	 * </p>
-	 *
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		If {@link #cache()} or {@link RestResponse#cacheBody()} has been called, this method can be can be called multiple times and/or combined with
-	 *		other methods that retrieve the content of the response.  Otherwise a {@link RestCallException}
-	 *		with an inner {@link IllegalStateException} will be thrown.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @param m The mutable to set the value in.
-	 * @param regex The regular expression pattern to match.
-	 * @param flags Pattern match flags.  See {@link Pattern#compile(String, int)}.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If a connection error occurred.
-	 */
-	public RestResponse asMatcher(Mutable<Matcher> m, String regex, int flags) throws RestCallException {
-		asMatcher(m, Pattern.compile(regex, flags));
-		return response;
-	}
-
-	/**
 	 * Returns the response that created this object.
 	 *
 	 * @return The response that created this object.
@@ -1531,38 +1322,38 @@ public class ResponseBody implements HttpEntity {
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().equals(<js>"OK"</js>);
+	 * 		.getBody().assertValue().equals(<js>"OK"</js>);
 	 *
 	 * 	<jc>// Validates the response body contains the text "OK".</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().contains(<js>"OK"</js>);
+	 * 		.getBody().assertValue().contains(<js>"OK"</js>);
 	 *
 	 * 	<jc>// Validates the response body passes a predicate test.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().passes(<jv>x</jv> -&gt; <jv>x</jv>.contains(<js>"OK"</js>));
+	 * 		.getBody().assertValue().passes(<jv>x</jv> -&gt; <jv>x</jv>.contains(<js>"OK"</js>));
 	 *
 	 * 	<jc>// Validates the response body matches a regular expression.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().matches(<js>".*OK.*"</js>);
+	 * 		.getBody().assertValue().matches(<js>".*OK.*"</js>);
 	 *
 	 * 	<jc>// Validates the response body matches a regular expression using regex flags.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().matches(<js>".*OK.*"</js>,  <jsf>MULTILINE</jsf> &amp; <jsf>CASE_INSENSITIVE</jsf>);
+	 * 		.getBody().assertValue().matches(<js>".*OK.*"</js>,  <jsf>MULTILINE</jsf> &amp; <jsf>CASE_INSENSITIVE</jsf>);
 	 *
 	 * 	<jc>// Validates the response body matches a regular expression in the form of an existing Pattern.</jc>
 	 * 	Pattern <jv>p</jv> = Pattern.<jsm>compile</jsm>(<js>".*OK.*"</js>);
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().matches(<jv>p</jv>);
+	 * 		.getBody().assertValue().matches(<jv>p</jv>);
 	 * </p>
 	 *
 	 * <p>
@@ -1572,8 +1363,8 @@ public class ResponseBody implements HttpEntity {
 	 * 	MyBean <jv>bean</jv> = <jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertBody().matches(<js>".*OK.*"</js>);
-	 * 		.assertBody().doesNotMatch(<js>".*ERROR.*"</js>)
+	 * 		.getBody().assertValue().matches(<js>".*OK.*"</js>);
+	 * 		.getBody().assertValue().doesNotMatch(<js>".*ERROR.*"</js>)
 	 * 		.getBody().as(MyBean.<jk>class</jk>);
 	 * </p>
 	 *
@@ -1589,76 +1380,8 @@ public class ResponseBody implements HttpEntity {
 	 * @return A new fluent assertion object.
 	 * @throws RestCallException If REST call failed.
 	 */
-	public FluentStringAssertion<RestResponse> assertString() throws RestCallException {
-		return new FluentStringAssertion<>(asString(), response);
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on the bytes of the response body.
-	 *
-	 * <p>
-	 * This method is called directly from the {@link RestResponse#assertBodyBytes()} method to instantiate a fluent assertions object.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates the response body equals the text "foo".</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.assertBodyBytes().hex().is(<js>"666F6F"</js>);
-	 * </p>
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @return A new fluent assertion object.
-	 * @throws RestCallException If REST call failed.
-	 */
-	public FluentByteArrayAssertion<RestResponse> assertBytes() throws RestCallException {
-		return new FluentByteArrayAssertion<>(asBytes(), response);
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on this response body.
-	 *
-	 * <p>
-	 * This method is called directly from the {@link RestResponse#assertBody(Class)} method to instantiate a fluent assertions object.
-	 *
-	 * <p>
-	 * Converts the body of the response to the specified object using {@link #as(Class)} and returns it as a fluent assertions object.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates the response body bean is the expected value.</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<js>"/myBean"</js>)
-	 * 		.run()
-	 * 		.assertBody(MyBean.<jk>class</jk>).json().is(<js>"{foo:'bar'}"</js>);
-	 * </p>
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		If {@link #cache()} or {@link RestResponse#cacheBody()} has been called, this method can be can be called multiple times and/or combined with
-	 *		other methods that retrieve the content of the response.  Otherwise a {@link RestCallException}
-	 *		with an inner {@link IllegalStateException} will be thrown.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @param type The object type to create.
-	 * @return A new fluent assertion object.
-	 * @throws RestCallException If REST call failed.
-	 */
-	public <T> FluentObjectAssertion<T,RestResponse> assertObject(Class<T> type) throws RestCallException {
-		return new FluentObjectAssertion<>(as(type), response);
+	public FluentResponseBodyAssertion<RestResponse> assertValue() throws RestCallException {
+		return new FluentResponseBodyAssertion<>(this, response);
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseHeader.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseHeader.java
index 3d4a079..b16f81b 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseHeader.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/ResponseHeader.java
@@ -22,13 +22,13 @@ import java.util.regex.*;
 
 import org.apache.http.*;
 import org.apache.juneau.*;
-import org.apache.juneau.assertions.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.http.header.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.oapi.*;
 import org.apache.juneau.parser.ParseException;
 import org.apache.juneau.reflect.*;
+import org.apache.juneau.rest.client.assertion.*;
 import org.apache.juneau.utils.*;
 
 /**
@@ -421,34 +421,6 @@ public class ResponseHeader implements Header {
 	}
 
 	/**
-	 * Same as {@link #asMatcher(Pattern)} but sets the value in a mutable for fluent calls.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Parse header using a regular expression.</jc>
-	 * 	Mutable&lt;Matcher&gt; <jv>mutable</jv> = Mutable.<jsm>create</jsm>();
-	 *
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getHeader(<js>"Content-Type"</js>).asMatcher(<jv>mutable</jv>, Pattern.<jsm>compile</jsm>(<js>"application/(.*)"</js>));
-	 *
-	 * 	<jk>if</jk> (<jv>mutable</jv>.get().matches()) {
-	 * 		String <jv>mediaType</jv> = <jv>mutable</jv>.get().group(1);
-	 * 	}
-	 * </p>
-	 *
-	 * @param m The mutable to set the value in.
-	 * @param pattern The regular expression pattern to match.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If a connection error occurred.
-	 */
-	public RestResponse asMatcher(Mutable<Matcher> m, Pattern pattern) throws RestCallException {
-		m.set(pattern.matcher(asString().orElse("")));
-		return response;
-	}
-
-	/**
 	 * Matches the specified pattern against this header value.
 	 *
 	 * <h5 class='section'>Example:</h5>
@@ -473,34 +445,6 @@ public class ResponseHeader implements Header {
 	}
 
 	/**
-	 * Same as {@link #asMatcher(String)} but sets the value in a mutable for fluent calls.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Parse header using a regular expression.</jc>
-	 * 	Mutable&lt;Matcher&gt; <jv>mutable</jv> = Mutable.<jsm>create</jsm>();
-	 *
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getHeader(<js>"Content-Type"</js>).asMatcher(<jv>mutable</jv>, <js>"application/(.*)"</js>);
-	 *
-	 * 	<jk>if</jk> (<jv>mutable</jv>.get().matches()) {
-	 * 		String <jv>mediaType</jv> = <jv>mutable</jv>.get().group(1);
-	 * 	}
-	 * </p>
-	 *
-	 * @param m The mutable to set the value in.
-	 * @param regex The regular expression pattern to match.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If a connection error occurred.
-	 */
-	public RestResponse asMatcher(Mutable<Matcher> m, String regex) throws RestCallException {
-		m.set(asMatcher(regex, 0));
-		return response;
-	}
-
-	/**
 	 * Matches the specified pattern against this header value.
 	 *
 	 * <h5 class='section'>Example:</h5>
@@ -526,35 +470,6 @@ public class ResponseHeader implements Header {
 	}
 
 	/**
-	 * Same as {@link #asMatcher(String,int)} but sets the value in a mutable for fluent calls.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Parse header using a regular expression.</jc>
-	 * 	Mutable&lt;Matcher&gt; <jv>mutable</jv> = Mutable.<jsm>create</jsm>();
-	 *
-	 * 	client
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getHeader(<js>"Content-Type"</js>).asMatcher(<jv>mutable</jv>, <js>"application/(.*)"</js>, <jsf>CASE_INSENSITIVE</jsf>);
-	 *
-	 * 	<jk>if</jk> (<jv>mutable</jv>.get().matches()) {
-	 * 		String <jv>mediaType</jv> = <jv>mutable</jv>.get().group(1);
-	 * 	}
-	 * </p>
-	 *
-	 * @param m The mutable to set the value in.
-	 * @param regex The regular expression pattern to match.
-	 * @param flags Pattern match flags.  See {@link Pattern#compile(String, int)}.
-	 * @return The response object (for method chaining).
-	 * @throws RestCallException If a connection error occurred.
-	 */
-	public RestResponse asMatcher(Mutable<Matcher> m, String regex, int flags) throws RestCallException {
-		m.set(asMatcher(Pattern.compile(regex, flags)));
-		return response;
-	}
-
-	/**
 	 * Returns the response that created this object.
 	 *
 	 * @return The response that created this object.
@@ -576,37 +491,37 @@ public class ResponseHeader implements Header {
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).exists();
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().exists();
 	 *
 	 * 	<jc>// Validates the content type is JSON.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).equals(<js>"application/json"</js>);
+	 * 		.getHeader(<js>"Content-Type"</js>).equals(<js>"application/json"</js>);
 	 *
 	 * 	<jc>// Validates the content type is JSON using test predicate.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).passes(<jv>x</jv> -&gt; <jv>x</jv>.equals(<js>"application/json"</js>));
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().passes(<jv>x</jv> -&gt; <jv>x</jv>.equals(<js>"application/json"</js>));
 	 *
 	 * 	<jc>// Validates the content type is JSON by just checking for substring.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).contains(<js>"json"</js>);
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().contains(<js>"json"</js>);
 	 *
 	 * 	<jc>// Validates the content type is JSON using regular expression.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).matches(<js>".*json.*"</js>);
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().matches(<js>".*json.*"</js>);
 	 *
 	 * 	<jc>// Validates the content type is JSON using case-insensitive regular expression.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).matches(<js>".*json.*"</js>, <jsf>CASE_INSENSITIVE</jsf>);
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().matches(<js>".*json.*"</js>, <jsf>CASE_INSENSITIVE</jsf>);
 	 * </p>
 	 *
 	 * <p>
@@ -616,87 +531,15 @@ public class ResponseHeader implements Header {
 	 * 	MediaType <jv>mediaType</jv> = <jv>client</jv>
 	 * 		.get(<jsf>URI</jsf>)
 	 * 		.run()
-	 * 		.assertHeader(<js>"Content-Type"</js>).exists()
-	 * 		.assertHeader(<js>"Content-Type"</js>).matches(<js>".*json.*"</js>)
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().exists()
+	 * 		.getHeader(<js>"Content-Type"</js>).assertValue().matches(<js>".*json.*"</js>)
 	 * 		.getHeader(<js>"Content-Type"</js>).as(MediaType.<jk>class</jk>);
 	 * </p>
 	 *
 	 * @return A new fluent assertion object.
 	 */
-	public FluentStringAssertion<RestResponse> assertString() {
-		return new FluentStringAssertion<>(asString().orElse(null), response);
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on an integer response header.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the response content age is greater than 1.</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.assertIntegerHeader(<js>"Age"</js>).isGreaterThan(1);
-	 * </p>
-	 *
-	 * @return A new fluent assertion object.
-	 */
-	public FluentIntegerAssertion<RestResponse> assertInteger() {
-		return new FluentIntegerAssertion<>(asIntegerHeader().asInteger().orElse(null), response);
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on a long response header.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the response body is not too long.</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.assertLongHeader(<js>"Length"</js>).isLessThan(100000);
-	 * </p>
-	 *
-	 * @return A new fluent assertion object.
-	 */
-	public FluentLongAssertion<RestResponse> assertLong() {
-		return new FluentLongAssertion<>(asLongHeader().asLong().orElse(null), response);
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on a date response header.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the response content is not expired.</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getHeader(<js>"Expires"</js>).assertDate().isAfterNow();
-	 * </p>
-	 *
-	 * @return A new fluent assertion object.
-	 */
-	public FluentZonedDateTimeAssertion<RestResponse> assertDate() {
-		return new FluentZonedDateTimeAssertion<>(asDateHeader().asZonedDateTime().orElse(null), response);
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on comma-separated string headers.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the response content is not expired.</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.getHeader(<js>"Allow"</js>).assertCsvArray().contains(<js>"GET"</js>);
-	 * </p>
-	 *
-	 * @return A new fluent assertion object.
-	 */
-	public FluentListAssertion<RestResponse> assertCsvArray() {
-		return new FluentListAssertion<>(asCsvArrayHeader().asList().orElse(null), response);
+	public FluentResponseHeaderAssertion<RestResponse> assertValue() {
+		return new FluentResponseHeaderAssertion<>(this, response);
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index a35243d..81cb4eb 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -48,7 +48,6 @@ import org.apache.http.params.*;
 import org.apache.http.protocol.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.assertions.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.cp.*;
 import org.apache.juneau.http.remote.RemoteReturn;
@@ -63,6 +62,7 @@ import org.apache.juneau.oapi.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.parser.ParseException;
 import org.apache.juneau.reflect.*;
+import org.apache.juneau.rest.client.assertion.*;
 import org.apache.juneau.rest.client.remote.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.urlencoding.*;
@@ -639,10 +639,7 @@ import org.apache.juneau.utils.*;
  * <ul class='javatree'>
  * 	<li class='jc'>{@link ResponseHeader}
  * 	<ul>
- * 		<li class='jm'><c>{@link ResponseHeader#assertString() assertString()} <jk>returns</jk> {@link FluentStringAssertion}</c>
- * 		<li class='jm'><c>{@link ResponseHeader#assertInteger() assertInteger()} <jk>returns</jk> {@link FluentIntegerAssertion}</c>
- * 		<li class='jm'><c>{@link ResponseHeader#assertLong() assertLong()} <jk>returns</jk> {@link FluentLongAssertion}</c>
- * 		<li class='jm'><c>{@link ResponseHeader#assertDate() assertDate()} <jk>returns</jk> {@link FluentDateAssertion}</c>
+ * 		<li class='jm'><c>{@link ResponseHeader#assertValue() assertValue()} <jk>returns</jk> {@link FluentResponseHeaderAssertion}</c>
  * 	</ul>
  * </ul>
  *
@@ -655,7 +652,7 @@ import org.apache.juneau.utils.*;
  * 	<jc>// Assert the response content type is any sort of JSON.</jc>
  * 	String <jv>body</jv> = <jv>client</jv>.get(<jsf>URI</jsf>)
  * 		.run()
- * 		.getHeader(<js>"Content-Type"</js>).assertString().matchesSimple(<js>"application/json*"</js>)
+ * 		.getHeader(<js>"Content-Type"</js>).assertValue().matchesSimple(<js>"application/json*"</js>)
  * 		.getBody().asString();
  * </p>
  *
@@ -756,9 +753,7 @@ import org.apache.juneau.utils.*;
  * <ul class='javatree'>
  * 	<li class='jc'>{@link ResponseBody}
  * 	<ul>
- * 		<li class='jm'><c>{@link ResponseBody#assertString() assertString()} <jk>returns</jk> {@link FluentStringAssertion}</c>
- * 		<li class='jm'><c>{@link ResponseBody#assertObject(Class) assertObject(Class&lt;?&gt;)} <jk>returns</jk> {@link FluentObjectAssertion}</c>
- * 		<li class='jm'><c>{@link ResponseBody#assertBytes() assertBytes()} <jk>returns</jk> {@link FluentByteArrayAssertion}</c>
+ * 		<li class='jm'><c>{@link ResponseBody#assertValue() assertValue()} <jk>returns</jk> {@link FluentResponseBodyAssertion}</c>
  * 	</ul>
  * </ul>
  *
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 78b44e8..1c1a4bf 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
@@ -417,70 +417,8 @@ public class RestResponse implements HttpResponse {
 	 * @return A new fluent assertion object.
 	 * @throws RestCallException If REST call failed.
 	 */
-	public FluentStringAssertion<RestResponse> assertBody() throws RestCallException {
-		return responseBody.cache().assertString();
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on the bytes of the response body.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates the response body equals the text "foo".</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<jsf>URI</jsf>)
-	 * 		.run()
-	 * 		.assertBodyBytes().hex().is(<js>"666F6F"</js>);
-	 * </p>
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @return A new fluent assertion object.
-	 * @throws RestCallException If REST call failed.
-	 */
-	public FluentByteArrayAssertion<RestResponse> assertBodyBytes() throws RestCallException {
-		return responseBody.cache().assertBytes();
-	}
-
-	/**
-	 * Provides the ability to perform fluent-style assertions on this response body.
-	 *
-	 * <p>
-	 * <p>
-	 * Combines the functionality of {@link ResponseBody#as(Class)} with {@link #assertBody()} by converting the body to the specified
-	 * bean and then serializing it to simplified JSON for easy string comparison.
-	 *
-	 * <h5 class='section'>Examples:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates the response body bean is the expected value.</jc>
-	 * 	<jv>client</jv>
-	 * 		.get(<js>"/myBean"</js>)
-	 * 		.run()
-	 * 		.assertBody(MyBean.<jk>class</jk>).json().is(<js>"{foo:'bar'}"</js>);
-	 * </p>
-	 *
-	 * <ul class='notes'>
-	 * 	<li>
-	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
-	 *  <li>
-	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
-	 * 	<li>
-	 * 		The input stream is automatically closed after this call.
-	 * </ul>
-	 *
-	 * @param type The object type to create.
-	 * @return A new fluent assertion object.
-	 * @throws RestCallException If REST call failed.
-	 */
-	public <V> FluentObjectAssertion<V,RestResponse> assertBody(Class<V> type) throws RestCallException {
-		return responseBody.cache().assertObject(type);
+	public FluentResponseBodyAssertion<RestResponse> assertBody() throws RestCallException {
+		return new FluentResponseBodyAssertion<>(responseBody, this);
 	}
 
 	/**
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java
new file mode 100644
index 0000000..d113748
--- /dev/null
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java
@@ -0,0 +1,306 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau.rest.client.assertion;
+
+import java.util.function.*;
+
+import org.apache.juneau.assertions.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.client.*;
+
+/**
+ * Used for fluent assertion calls against {@link ResponseBody} objects.
+ *
+ * @param <R> The return type.
+ */
+@FluentSetters(returns="FluentResponseBodyAssertion<R>")
+public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
+
+	private final ResponseBody value;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param value The object being tested.
+	 * @param returns The object to return after the test.
+	 */
+	public FluentResponseBodyAssertion(ResponseBody value, R returns) {
+		this(null, value, returns);
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @param creator The assertion that created this assertion.
+	 * @param value The object being tested.
+	 * @param returns The object to return after the test.
+	 */
+	public FluentResponseBodyAssertion(Assertion creator, ResponseBody value, R returns) {
+		super(creator, returns);
+		this.value = value;
+	}
+
+	/**
+	 * Provides the ability to perform fluent-style assertions on this response body.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the response body equals the text "OK".</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().is(<js>"OK"</js>);
+	 *
+	 * 	<jc>// Validates the response body contains the text "OK".</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().contains(<js>"OK"</js>);
+	 *
+	 * 	<jc>// Validates the response body passes a predicate test.</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().passes(<jv>x</jv> -&gt; <jv>x</jv>.contains(<js>"OK"</js>));
+	 *
+	 * 	<jc>// Validates the response body matches a regular expression.</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().matches(<js>".*OK.*"</js>);
+	 *
+	 * 	<jc>// Validates the response body matches a regular expression using regex flags.</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().matches(<js>".*OK.*"</js>, <jsf>MULTILINE</jsf> &amp; <jsf>CASE_INSENSITIVE</jsf>);
+	 *
+	 * 	<jc>// Validates the response body matches a regular expression in the form of an existing Pattern.</jc>
+	 * 	Pattern <jv>p</jv> = Pattern.<jsm>compile</jsm>(<js>".*OK.*"</js>);
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().matches(<jv>p</jv>);
+	 * </p>
+	 *
+	 * <p>
+	 * The assertion test returns the original response object allowing you to chain multiple requests like so:
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the response body matches a regular expression.</jc>
+	 * 	MyBean <jv>bean</jv> = <jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBody().matches(<js>".*OK.*"</js>);
+	 * 		.assertBody().doesNotMatch(<js>".*ERROR.*"</js>)
+	 * 		.getBody().as(MyBean.<jk>class</jk>);
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
+	 *  <li>
+	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
+	 * 	<li>
+	 * 		The input stream is automatically closed after this call.
+	 * </ul>
+	 *
+	 * @return A new fluent assertion object.
+	 */
+	public FluentStringAssertion<R> asString() {
+		return new FluentStringAssertion<>(valueAsString(), returns());
+	}
+
+	/**
+	 * Provides the ability to perform fluent-style assertions on the bytes of the response body.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the response body equals the text "foo".</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<jsf>URI</jsf>)
+	 * 		.run()
+	 * 		.assertBodyBytes().hex().is(<js>"666F6F"</js>);
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
+	 *  <li>
+	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
+	 * 	<li>
+	 * 		The input stream is automatically closed after this call.
+	 * </ul>
+	 *
+	 * @return A new fluent assertion object.
+	 * @throws RestCallException If REST call failed.
+	 */
+	public FluentByteArrayAssertion<R> asBytes() throws RestCallException {
+		return new FluentByteArrayAssertion<>(valueAsBytes(), returns());
+	}
+
+	/**
+	 * Provides the ability to perform fluent-style assertions on this response body.
+	 *
+	 * <p>
+	 * Converts the body to a type using {@link ResponseBody#as(Class)} and then returns the value as an object assertion.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the response body bean is the expected value.</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<js>"/myBean"</js>)
+	 * 		.run()
+	 * 		.assertBody().asType(MyBean.<jk>class</jk>).json().is(<js>"{foo:'bar'}"</js>);
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
+	 *  <li>
+	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
+	 * 	<li>
+	 * 		The input stream is automatically closed after this call.
+	 * </ul>
+	 *
+	 * @param type The object type to create.
+	 * @return A new fluent assertion object.
+	 * @throws RestCallException If REST call failed.
+	 */
+	public <V> FluentObjectAssertion<V,R> asType(Class<V> type) throws RestCallException {
+		return new FluentObjectAssertion<>(valueAsType(type), returns());
+	}
+
+	/**
+	 * Asserts that the value equals the specified value.
+	 *
+	 * @param value The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isEqual(String value) throws AssertionError {
+		return asString().isEqual(value);
+	}
+
+	/**
+	 * Asserts that the body contains the specified value.
+	 *
+	 * @param values The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R is(String values) throws AssertionError {
+		return asString().is(values);
+	}
+
+	/**
+	 * Asserts that the text contains all of the specified substrings.
+	 *
+	 * @param values The values to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R contains(String...values) throws AssertionError {
+		return asString().contains(values);
+	}
+
+	/**
+	 * Asserts that the body doesn't contain any of the specified substrings.
+	 *
+	 * @param values The values to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R doesNotContain(String...values) throws AssertionError {
+		return asString().doesNotContain(values);
+	}
+
+	/**
+	 * Asserts that the body is empty.
+	 *
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isEmpty() {
+		return asString().isEmpty();
+	}
+
+	/**
+	 * Asserts that the body is not empty.
+	 *
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isNotEmpty() {
+		return asString().isNotEmpty();
+	}
+
+	/**
+	 * Asserts that the value passes the specified predicate test.
+	 *
+	 * @param test The predicate to use to test the value.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R passes(Predicate<String> test) throws AssertionError {
+		if (! test.test(valueAsString()))
+			throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
+		return returns();
+	}
+
+	private String valueAsString() throws AssertionError {
+		try {
+			return value.cache().asString();
+		} catch (RestCallException e) {
+			throw error(e, "Exception occurred during call.");
+		}
+	}
+
+	private byte[] valueAsBytes() throws AssertionError {
+		try {
+			return value.cache().asBytes();
+		} catch (RestCallException e) {
+			throw error(e, "Exception occurred during call.");
+		}
+	}
+
+	private <T> T valueAsType(Class<T> c) throws AssertionError {
+		try {
+			return value.cache().as(c);
+		} catch (RestCallException e) {
+			throw error(e, "Exception occurred during call.");
+		}
+	}
+
+	// <FluentSetters>
+
+	@Override /* GENERATED - Assertion */
+	public FluentResponseBodyAssertion<R> msg(String msg, Object...args) {
+		super.msg(msg, args);
+		return this;
+	}
+
+	@Override /* GENERATED - Assertion */
+	public FluentResponseBodyAssertion<R> stderr() {
+		super.stderr();
+		return this;
+	}
+
+	@Override /* GENERATED - Assertion */
+	public FluentResponseBodyAssertion<R> stdout() {
+		super.stdout();
+		return this;
+	}
+
+
+	// </FluentSetters>
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_BasicCalls_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_BasicCalls_Test.java
index 648b1b3..0df76cc 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_BasicCalls_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_BasicCalls_Test.java
@@ -320,7 +320,7 @@ public class RestClient_BasicCalls_Test {
 			/*[15]*/ s2
 		);
 		for (int i = 0; i < bodies.size(); i++) {
-			client().header("Check","Content-Type").accept("application/json+simple").build().formPost("/checkHeader",bodies.get(i)).run().assertBody().msg("Body {0} failed",i).matchesSimple("['application/x-www-form-urlencoded*']");
+			client().header("Check","Content-Type").accept("application/json+simple").build().formPost("/checkHeader",bodies.get(i)).run().assertBody().msg("Body {0} failed",i).asString().matchesSimple("['application/x-www-form-urlencoded*']");
 			client().build().formPost("/bean",bodies.get(i)).accept("application/json+simple").run().assertBody().msg("Body {0} failed","#"+i).is("{f:1}");
 		}
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java
index a76f72a..f694ebf 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java
@@ -93,14 +93,14 @@ public class RestClient_Body_Test {
 		;
 
 		BasicHttpResource x7 = httpResource(new StringReader("foo"));
-		client().build().post("/",x7).run().getBody().assertString().is("foo");
+		client().build().post("/",x7).run().assertBody().is("foo");
 
 		BasicHttpResource x8 = httpResource(new StringReader("foo")).cache();
-		client().build().post("/",x8).run().getBody().assertString().is("foo");
-		client().build().post("/",x8).run().getBody().assertString().is("foo");
+		client().build().post("/",x8).run().assertBody().is("foo");
+		client().build().post("/",x8).run().assertBody().is("foo");
 
 		BasicHttpResource x9 = httpResource(null);
-		client().build().post("/",x9).run().getBody().assertString().isEmpty();
+		client().build().post("/",x9).run().assertBody().isEmpty();
 	}
 
 	@Test
@@ -138,14 +138,14 @@ public class RestClient_Body_Test {
 		;
 
 		BasicHttpEntity x7 = httpEntity(new StringReader("foo"));
-		client().build().post("/",x7).run().getBody().assertString().is("foo");
+		client().build().post("/",x7).run().assertBody().is("foo");
 
 		BasicHttpEntity x8 = httpEntity(new StringReader("foo")).cache();
-		client().build().post("/",x8).run().getBody().assertString().is("foo");
-		client().build().post("/",x8).run().getBody().assertString().is("foo");
+		client().build().post("/",x8).run().assertBody().is("foo");
+		client().build().post("/",x8).run().assertBody().is("foo");
 
 		BasicHttpEntity x9 = httpEntity(null);
-		client().build().post("/",x9).run().getBody().assertString().isEmpty();
+		client().build().post("/",x9).run().assertBody().isEmpty();
 
 		BasicHttpEntity x12 = httpEntity("foo");
 		x12.assertString().is("foo");
@@ -170,35 +170,35 @@ public class RestClient_Body_Test {
 			.assertHeader("X-Content-Length").doesNotExist()
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").is("application/json")
-			.getBody().assertObject(ABean.class).asJson().is("{a:1,b:'foo'}");
+			.assertBody().asType(ABean.class).asJson().is("{a:1,b:'foo'}");
 
 		SerializedHttpEntity x3 = SerializedHttpEntity.of(()->ABean.get(),js);
 		client().build().post("/",x3).run()
 			.assertHeader("X-Content-Length").doesNotExist()
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").is("application/json")
-			.getBody().assertObject(ABean.class).asJson().is("{a:1,b:'foo'}");
+			.assertBody().asType(ABean.class).asJson().is("{a:1,b:'foo'}");
 
 		SerializedHttpEntity x4 = serializedHttpEntity(new StringReader("{a:1,b:'foo'}"),null);
 		client().build().post("/",x4).run()
 			.assertHeader("X-Content-Length").doesNotExist()
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").doesNotExist()
-			.getBody().assertObject(ABean.class).asJson().is("{a:1,b:'foo'}");
+			.assertBody().asType(ABean.class).asJson().is("{a:1,b:'foo'}");
 
 		SerializedHttpEntity x5 = serializedHttpEntity(new ByteArrayInputStream("{a:1,b:'foo'}".getBytes()),null);
 		client().build().post("/",x5).run()
 			.assertHeader("X-Content-Length").doesNotExist()
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").doesNotExist()
-			.getBody().assertObject(ABean.class).asJson().is("{a:1,b:'foo'}");
+			.assertBody().asType(ABean.class).asJson().is("{a:1,b:'foo'}");
 
 		SerializedHttpEntity x6 = serializedHttpEntity(f,null);
 		client().build().post("/",x6).run()
 			.assertHeader("X-Content-Length").is("0")
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").doesNotExist()
-			.getBody().assertObject(ABean.class).asJson().is("{a:0}");
+			.assertBody().asType(ABean.class).asJson().is("{a:0}");
 
 		InputStream x7 = new ByteArrayInputStream("foo".getBytes()) {
 			@Override
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_BeanContext_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_BeanContext_Test.java
index 9040cf4..023670d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_BeanContext_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_BeanContext_Test.java
@@ -69,9 +69,9 @@ public class RestClient_Config_BeanContext_Test {
 		x1.post("/echoBody",new A1()).run().assertBody().is("'O1'");
 		x2.post("/echoBody",new A1()).run().assertBody().is("{f:1}");
 		x1.get("/checkQuery").query("foo",new A1()).run().assertBody().is("foo=O1");
-		x2.get("/checkQuery").query("foo",new A1()).run().assertBody().is("foo=f%3D1").assertBody().urlDecode().is("foo=f=1");
+		x2.get("/checkQuery").query("foo",new A1()).run().assertBody().is("foo=f%3D1").assertBody().asString().urlDecode().is("foo=f=1");
 		x1.formPost("/checkFormData").formData("foo",new A1()).run().assertBody().is("foo=O1");
-		x2.formPost("/checkFormData").formData("foo",new A1()).run().assertBody().is("foo=f%3D1").assertBody().urlDecode().is("foo=f=1");
+		x2.formPost("/checkFormData").formData("foo",new A1()).run().assertBody().is("foo=f%3D1").assertBody().asString().urlDecode().is("foo=f=1");
 		x1.get("/checkHeader").header("foo",new A1()).header("Check","foo").run().assertBody().is("['O1']");
 		x2.get("/checkHeader").header("foo",new A1()).header("Check","foo").run().assertBody().is("['f=1']");
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_OpenApi_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_OpenApi_Test.java
index c23e276..455ea2a 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_OpenApi_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_OpenApi_Test.java
@@ -50,15 +50,15 @@ public class RestClient_Config_OpenApi_Test {
 
 	@Test
 	public void a01_oapiFormat() throws Exception {
-		client().oapiFormat(HttpPartFormat.UON).build().get("/checkQuery").query("Foo","bar baz").run().assertBody().urlDecode().is("Foo='bar baz'");
+		client().oapiFormat(HttpPartFormat.UON).build().get("/checkQuery").query("Foo","bar baz").run().assertBody().asString().urlDecode().is("Foo='bar baz'");
 	}
 
 	@Test
 	public void a02_oapiCollectionFormat() throws Exception {
 		String[] a = {"bar","baz"};
 		RestClient x = client().oapiCollectionFormat(PIPES).build();
-		x.get("/checkQuery").query("Foo",a).run().assertBody().urlDecode().is("Foo=bar|baz");
-		x.post("/checkFormData").formData("Foo",a).run().assertBody().urlDecode().is("Foo=bar|baz");
+		x.get("/checkQuery").query("Foo",a).run().assertBody().asString().urlDecode().is("Foo=bar|baz");
+		x.post("/checkFormData").formData("Foo",a).run().assertBody().asString().urlDecode().is("Foo=bar|baz");
 		x.get("/checkHeader").header("Check","Foo").header("Foo",a).accept("text/json+simple").run().assertBody().is("['bar|baz']");
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java
index 94fdfe6..5eb3695 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java
@@ -115,10 +115,10 @@ public class RestClient_FormData_Test {
 		List<String> l1 = AList.of("bar1","bar2"), l2 = AList.of("qux1","qux2");
 
 		client().formDataPairs("foo","bar","baz","qux").build().post("/formData").run().assertBody().is("foo=bar&baz=qux");
-		client().formDataPairs("foo",l1,"baz",l2).build().post("/formData").run().assertBody().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
+		client().formDataPairs("foo",l1,"baz",l2).build().post("/formData").run().assertBody().asString().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
 
 		client().build().post("/formData").formDataPairs("foo","bar","baz","qux").run().assertBody().is("foo=bar&baz=qux");
-		client().build().post("/formData").formDataPairs("foo",l1,"baz",l2).run().assertBody().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
+		client().build().post("/formData").formDataPairs("foo",l1,"baz",l2).run().assertBody().asString().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
 
 		assertThrown(()->client().formDataPairs("foo","bar","baz")).is("Odd number of parameters passed into formDataPairs()");
 		assertThrown(()->client().build().post("").formDataPairs("foo","bar","baz")).is("Odd number of parameters passed into formDataPairs()");
@@ -127,21 +127,21 @@ public class RestClient_FormData_Test {
 	@Test
 	public void a06_formData_String_Object_Schema() throws Exception {
 		List<String> l = AList.of("bar","baz"), l2 = AList.of("qux","quux");
-		client().formData("foo",l,T_ARRAY_PIPES).build().post("/formData").formData("foo",l2,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=bar|baz&foo=qux|quux");
+		client().formData("foo",l,T_ARRAY_PIPES).build().post("/formData").formData("foo",l2,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=bar|baz&foo=qux|quux");
 	}
 
 	@Test
 	public void a07_formData_String_Object_Schema_Serializer() throws Exception {
 		List<String> l = AList.of("bar","baz");
-		client().formData("foo",l,T_ARRAY_PIPES,UonSerializer.DEFAULT).build().post("/formData").run().assertBody().urlDecode().is("foo=@(bar,baz)");
+		client().formData("foo",l,T_ARRAY_PIPES,UonSerializer.DEFAULT).build().post("/formData").run().assertBody().asString().urlDecode().is("foo=@(bar,baz)");
 	}
 
 	@Test
 	public void a08_formData_AddFlag_String_Object_Schema() throws Exception {
 		List<String> l = AList.of("qux","quux");
-		client().formData("foo","bar").build().post("/formData").formData(APPEND,"foo",l,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=bar&foo=qux|quux");
-		client().formData("foo","bar").build().post("/formData").formData(PREPEND,"foo",l,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=qux|quux&foo=bar");
-		client().formData("foo","bar").build().post("/formData").formData(REPLACE,"foo",l,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=qux|quux");
+		client().formData("foo","bar").build().post("/formData").formData(APPEND,"foo",l,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=bar&foo=qux|quux");
+		client().formData("foo","bar").build().post("/formData").formData(PREPEND,"foo",l,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=qux|quux&foo=bar");
+		client().formData("foo","bar").build().post("/formData").formData(REPLACE,"foo",l,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=qux|quux");
 	}
 
 	@Test
@@ -150,15 +150,15 @@ public class RestClient_FormData_Test {
 
 		RestClient x1 = client().formData("foo",s).build();
 		s.set(OList.of("foo","bar"));
-		x1.post("/formData").run().assertBody().urlDecode().is("foo=foo,bar");
+		x1.post("/formData").run().assertBody().asString().urlDecode().is("foo=foo,bar");
 		s.set(OList.of("bar","baz"));
-		x1.post("/formData").run().assertBody().urlDecode().is("foo=bar,baz");
+		x1.post("/formData").run().assertBody().asString().urlDecode().is("foo=bar,baz");
 
 		RestClient x2 = client().build();
 		s.set(OList.of("foo","bar"));
-		x2.post("/formData").formData("foo",s).run().assertBody().urlDecode().is("foo=foo,bar");
+		x2.post("/formData").formData("foo",s).run().assertBody().asString().urlDecode().is("foo=foo,bar");
 		s.set(OList.of("bar","baz"));
-		x2.post("/formData").formData("foo",s).run().assertBody().urlDecode().is("foo=bar,baz");
+		x2.post("/formData").formData("foo",s).run().assertBody().asString().urlDecode().is("foo=bar,baz");
 	}
 
 	public static class A8 extends SimplePartSerializer {
@@ -177,9 +177,9 @@ public class RestClient_FormData_Test {
 	public void a10_formData_String_Supplier_Schema_Serializer() throws Exception {
 		TestSupplier s = TestSupplier.of(OList.of("foo","bar"));
 		RestClient x = client().formData("foo",s,T_ARRAY_PIPES,new A8()).build();
-		x.post("/formData").run().assertBody().urlDecode().is("foo=x['foo','bar']");
+		x.post("/formData").run().assertBody().asString().urlDecode().is("foo=x['foo','bar']");
 		s.set(OList.of("bar","baz"));
-		x.post("/formData").run().assertBody().urlDecode().is("foo=x['bar','baz']");
+		x.post("/formData").run().assertBody().asString().urlDecode().is("foo=x['bar','baz']");
 	}
 
 	@Test
@@ -189,15 +189,15 @@ public class RestClient_FormData_Test {
 
 		RestClient x1 = client().formData("foo",s,T_ARRAY_PIPES).build();
 		s.set(l1);
-		x1.post("/formData").run().assertBody().urlDecode().is("foo=foo|bar");
+		x1.post("/formData").run().assertBody().asString().urlDecode().is("foo=foo|bar");
 		s.set(l2);
-		x1.post("/formData").run().assertBody().urlDecode().is("foo=bar|baz");
+		x1.post("/formData").run().assertBody().asString().urlDecode().is("foo=bar|baz");
 
 		RestClient x2 = client().build();
 		s.set(l1);
-		x2.post("/formData").formData("foo",s,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=foo|bar");
+		x2.post("/formData").formData("foo",s,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=foo|bar");
 		s.set(l2);
-		x2.post("/formData").formData("foo",s,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=bar|baz");
+		x2.post("/formData").formData("foo",s,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=bar|baz");
 	}
 
 	public static class A12 implements HttpPartSerializer {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
index 285b8aa..499f958 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
@@ -57,14 +57,14 @@ public class RestClient_Query_Test {
 	@Test
 	public void a02_query_String_Object_Schema() throws Exception {
 		List<String> l = AList.of("bar","baz");
-		client().build().get("/query").query("foo",l,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=bar|baz");
-		client().query("foo",l,T_ARRAY_PIPES).build().get("/query").run().assertBody().urlDecode().is("foo=bar|baz");
+		client().build().get("/query").query("foo",l,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=bar|baz");
+		client().query("foo",l,T_ARRAY_PIPES).build().get("/query").run().assertBody().asString().urlDecode().is("foo=bar|baz");
 	}
 
 	@Test
 	public void a03_query_String_Object_Schema_Serializer() throws Exception {
 		List<String> l = AList.of("bar","baz");
-		client().query("foo",l,T_ARRAY_PIPES,UonSerializer.DEFAULT).build().get("/query").run().assertBody().urlDecode().is("foo=@(bar,baz)");
+		client().query("foo",l,T_ARRAY_PIPES,UonSerializer.DEFAULT).build().get("/query").run().assertBody().asString().urlDecode().is("foo=@(bar,baz)");
 	}
 
 	@Test
@@ -81,7 +81,7 @@ public class RestClient_Query_Test {
 	@Test
 	public void a05_query_AddFlag_String_Object_Schema() throws Exception {
 		List<String> l = AList.of("baz","qux");
-		client().query("foo","bar").build().get("/query").query(PREPEND,"foo",l,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=baz|qux&foo=bar");
+		client().query("foo","bar").build().get("/query").query(PREPEND,"foo",l,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=baz|qux&foo=bar");
 	}
 
 	@Test
@@ -89,9 +89,9 @@ public class RestClient_Query_Test {
 		List<String> l1 = AList.of("foo","bar"), l2 = AList.of("bar","baz");
 		TestSupplier s = TestSupplier.of(l1);
 		RestClient x = client().query("foo",s).build();
-		x.get("/query").run().assertBody().urlDecode().is("foo=foo,bar");
+		x.get("/query").run().assertBody().asString().urlDecode().is("foo=foo,bar");
 		s.set(l2);
-		x.get("/query").run().assertBody().urlDecode().is("foo=bar,baz");
+		x.get("/query").run().assertBody().asString().urlDecode().is("foo=bar,baz");
 	}
 
 	@Test
@@ -99,9 +99,9 @@ public class RestClient_Query_Test {
 		String[] l1 = new String[]{"foo","bar"},l2 = new String[]{"bar","baz"};
 		TestSupplier s = TestSupplier.of(l1);
 		RestClient x = client().query("foo",s,T_ARRAY_PIPES).build();
-		x.get("/query").query("bar",s,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=foo|bar&bar=foo|bar");
+		x.get("/query").query("bar",s,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=foo|bar&bar=foo|bar");
 		s.set(l2);
-		x.get("/query").query("bar",s,T_ARRAY_PIPES).run().assertBody().urlDecode().is("foo=bar|baz&bar=bar|baz");
+		x.get("/query").query("bar",s,T_ARRAY_PIPES).run().assertBody().asString().urlDecode().is("foo=bar|baz&bar=bar|baz");
 	}
 
 	public static class A8 extends SimplePartSerializer {
@@ -121,9 +121,9 @@ public class RestClient_Query_Test {
 		List<String> l1 = AList.of("foo","bar"), l2 = AList.of("bar","baz");
 		TestSupplier s = TestSupplier.of(l1);
 		RestClient x = client().query("foo",s,T_ARRAY_PIPES,new A8()).build();
-		x.get("/query").run().assertBody().urlDecode().is("foo=x['foo','bar']");
+		x.get("/query").run().assertBody().asString().urlDecode().is("foo=x['foo','bar']");
 		s.set(l2);
-		x.get("/query").run().assertBody().urlDecode().is("foo=x['bar','baz']");
+		x.get("/query").run().assertBody().asString().urlDecode().is("foo=x['bar','baz']");
 	}
 
 	@Test
@@ -167,8 +167,8 @@ public class RestClient_Query_Test {
 		List<String> l1 = AList.of("bar1","bar2"), l2 = AList.of("qux1","qux2");
 		client().queryPairs("foo","bar","baz","qux").build().get("/query").run().assertBody().is("foo=bar&baz=qux");
 		client().build().get("/query").queryPairs("foo","bar","baz","qux").run().assertBody().is("foo=bar&baz=qux");
-		client().queryPairs("foo",l1,"baz",l2).build().get("/query").run().assertBody().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
-		client().build().get("/query").queryPairs("foo",l1,"baz",l2).run().assertBody().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
+		client().queryPairs("foo",l1,"baz",l2).build().get("/query").run().assertBody().asString().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
+		client().build().get("/query").queryPairs("foo",l1,"baz",l2).run().assertBody().asString().urlDecode().is("foo=bar1,bar2&baz=qux1,qux2");
 		assertThrown(()->client().queryPairs("foo","bar","baz")).contains("Odd number of parameters");
 		assertThrown(()->client().build().get().queryPairs("foo","bar","baz")).contains("Odd number of parameters");
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java
index 70f01b0..6d42d29 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java
@@ -93,8 +93,8 @@ public class RestClient_Response_Body_Test {
 
 	@Test
 	public void a01_basic() throws Exception {
-		client().build().post("/echo",bean).run().assertBody(ABean.class).asJson().is("{f:1}");
-		client().build().post("/echo",bean).run().assertBodyBytes().asString().is("{f:1}");
+		client().build().post("/echo",bean).run().assertBody().asType(ABean.class).asJson().is("{f:1}");
+		client().build().post("/echo",bean).run().assertBody().asBytes().asString().is("{f:1}");
 	}
 
 	@Test
@@ -103,7 +103,7 @@ public class RestClient_Response_Body_Test {
 		ABean b = x.post("/echo",bean).run().getBody().parser(JsonParser.DEFAULT).as(ABean.class);
 		assertObject(b).asJson().is("{f:1}");
 		assertThrown(()->x.post("/echo",bean).run().getBody().parser(XmlParser.DEFAULT).as(ABean.class)).contains("ParseError at [row,col]:[1,1]");
-		assertThrown(()->x.post("/echo",bean).run().getBody().parser(XmlParser.DEFAULT).assertObject(ABean.class)).contains("ParseError at [row,col]:[1,1]");
+		assertThrown(()->x.post("/echo",bean).run().getBody().parser(XmlParser.DEFAULT).assertValue().asType(ABean.class)).contains("ParseError at [row,col]:[1,1]");
 	}
 
 	@Test
@@ -162,7 +162,7 @@ public class RestClient_Response_Body_Test {
 		byte[] x = client().build().get("/bean").run().getBody().asBytes();
 		assertBytes(x).asString().is("{f:1}");
 
-		x = client().build().get("/bean").run().getBody().cache().assertBytes().asString().is("{f:1}").getBody().asBytes();
+		x = client().build().get("/bean").run().assertBody().asBytes().asString().is("{f:1}").getBody().asBytes();
 		assertBytes(x).asString().is("{f:1}");
 
 		assertThrown(()->testClient().entity(new InputStreamEntity(badStream())).get().run().getBody().asBytes()).contains("foo");
@@ -229,8 +229,8 @@ public class RestClient_Response_Body_Test {
 		HttpEntity x6 = testClient().entity(stringEntity("{f:1}")).get().run().getBody().as(HttpEntity.class);
 		assertTrue(x6 instanceof ResponseBody);
 
-		plainTestClient().entity(stringEntity("foo")).get().run().getBody().assertObject(A7a.class).passes(x->((A7a)x).x.equals("foo"));
-		plainTestClient().entity(stringEntity("foo")).get().run().getBody().assertObject(A7b.class).passes(x->((A7b)x).x.equals("foo"));
+		plainTestClient().entity(stringEntity("foo")).get().run().assertBody().asType(A7a.class).passes(x->((A7a)x).x.equals("foo"));
+		plainTestClient().entity(stringEntity("foo")).get().run().assertBody().asType(A7b.class).passes(x->((A7b)x).x.equals("foo"));
 		assertThrown(()->plainTestClient().entity(stringEntity("foo")).headers(header("Content-Type","foo")).get().run().getBody().as(A7c.class)).exists().contains("Unsupported media-type","'foo'");
 		assertThrown(()->testClient().entity(stringEntity("")).get().run().getBody().as(A7c.class)).contains("foo");
 
@@ -241,24 +241,12 @@ public class RestClient_Response_Body_Test {
 		Future<ABean> x8 = testClient().entity(stringEntity("{f:1}")).get().run().getBody().asFuture(ABean.class);
 		assertObject(x8.get()).asJson().is("{f:1}");
 
-		Mutable<Future<ABean>> x9 = mutable();
-		testClient().entity(stringEntity("{f:1}")).get().run().getBody().asFuture(x9,ABean.class);
-		assertObject(x9.get().get()).asJson().is("{f:1}");
-
 		Future<ABean> x10 = testClient().entity(stringEntity("{f:1}")).get().run().getBody().asFuture(cm(ABean.class));
 		assertObject(x10.get()).asJson().is("{f:1}");
 
-		Mutable<Future<ABean>> x11 = mutable();
-		testClient().entity(stringEntity("{f:1}")).get().run().getBody().asFuture(x11,cm(ABean.class));
-		assertObject(x11.get().get()).asJson().is("{f:1}");
-
 		Future<List<Integer>> x12 = testClient().entity(stringEntity("[1,2]")).get().run().getBody().asFuture(List.class,Integer.class);
 		assertObject(x12.get()).asJson().is("[1,2]");
 
-		Mutable<Future<List<Integer>>> x13 = mutable();
-		testClient().entity(stringEntity("[1,2]")).get().run().getBody().asFuture(x13,List.class,Integer.class);
-		assertObject(x13.get().get()).asJson().is("[1,2]");
-
 		String x14 = testClient().entity(stringEntity("{f:1}")).get().run().getBody().asString();
 		assertString(x14).is("{f:1}");
 
@@ -300,19 +288,9 @@ public class RestClient_Response_Body_Test {
 		assertTrue(x24.matches());
 		assertString(x24.group(1)).is("123");
 
-		Mutable<Matcher> x25 = mutable();
-		testClient().entity(stringEntity("foo=123")).get().run().getBody().asMatcher(x25,Pattern.compile("foo=(.*)"));
-		assertTrue(x25.get().matches());
-		assertString(x25.get().group(1)).is("123");
-
 		Matcher x26 = testClient().entity(stringEntity("foo=123")).get().run().getBody().asMatcher("foo=(.*)");
 		assertTrue(x26.matches());
 		assertString(x26.group(1)).is("123");
-
-		Mutable<Matcher> x27 = mutable();
-		testClient().entity(stringEntity("foo=123")).get().run().getBody().asMatcher(x27,"foo=(.*)");
-		assertTrue(x27.get().matches());
-		assertString(x27.get().group(1)).is("123");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
@@ -334,14 +312,14 @@ public class RestClient_Response_Body_Test {
 
 		assertFalse(x2.isChunked());
 
-		testClient().entity(inputStreamEntity("foo")).get().run().getBody().getContentEncoding().assertString().isNull();
+		testClient().entity(inputStreamEntity("foo")).get().run().getBody().getContentEncoding().assertValue().isNull();
 
 		InputStreamEntity x3 = inputStreamEntity("foo");
 		x3.setContentType("text/foo");
 		x3.setContentEncoding("identity");
 		testClient().entity(x3).get().run().getBody().toResponse()
-			.getBody().getContentType().assertString().is("text/foo")
-			.getBody().getContentEncoding().assertString().is("identity");
+			.getBody().getContentType().assertValue().is("text/foo")
+			.getBody().getContentEncoding().assertValue().is("identity");
 
 		InputStream x4 = testClient().entity(inputStreamEntity("foo")).get().run().getBody().asInputStream();
 		assertStream(x4).asString().is("foo");
@@ -363,8 +341,8 @@ public class RestClient_Response_Body_Test {
 		assertFalse(client().build().head("").run().getBody().isRepeatable());
 		assertFalse(client().build().head("").run().getBody().isChunked());
 		assertLong(client().build().head("").run().getBody().getContentLength()).is(-1l);
-		client().build().head("").run().getBody().getContentType().assertString().isNull();
-		client().build().head("").run().getBody().getContentEncoding().assertString().isNull();
+		client().build().head("").run().getBody().getContentType().assertValue().isNull();
+		client().build().head("").run().getBody().getContentEncoding().assertValue().isNull();
 		client().build().head("").run().getBody().writeTo(new ByteArrayOutputStream());
 		assertFalse(client().build().head("").run().getBody().isStreaming());
 		client().build().head("").run().getBody().consumeContent();
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 d9b40f5..4eb867a 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
@@ -149,20 +149,6 @@ public class RestClient_Response_Headers_Test {
 		assertFalse(checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Bar").asMatcher("foo").matches());
 		assertTrue(checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Foo").asMatcher("FOO",Pattern.CASE_INSENSITIVE).matches());
 		assertFalse(checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Bar").asMatcher("FOO",Pattern.CASE_INSENSITIVE).matches());
-
-		Mutable<Matcher> m6 = Mutable.create();
-		checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Foo").asMatcher(m6,"foo");
-		assertTrue(m6.get().matches());
-		checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Bar").asMatcher(m6,"foo");
-		assertFalse(m6.get().matches());
-		checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Foo").asMatcher(m6,"FOO",Pattern.CASE_INSENSITIVE);
-		assertTrue(m6.get().matches());
-		checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Bar").asMatcher(m6,"FOO",Pattern.CASE_INSENSITIVE);
-		assertFalse(m6.get().matches());
-		checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Foo").asMatcher(m6,Pattern.compile("FOO",Pattern.CASE_INSENSITIVE));
-		assertTrue(m6.get().matches());
-		checkFooClient().build().get("/echo").header("Foo","foo").run().getResponseHeader("Bar").asMatcher(m6,Pattern.compile("FOO",Pattern.CASE_INSENSITIVE));
-		assertFalse(m6.get().matches());
 	}
 
 	@Test
@@ -177,14 +163,14 @@ public class RestClient_Response_Headers_Test {
 
 	@Test
 	public void b01_assertions() throws Exception {
-		checkFooClient().build().get("/echo").header("Foo","bar").run().getResponseHeader("Foo").assertString().is("bar");
-		checkFooClient().build().get("/echo").header("Foo","bar").run().getResponseHeader("Bar").assertString().doesNotExist();
-		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Foo").assertInteger().is(123);
-		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Bar").assertInteger().doesNotExist();
-		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Foo").assertLong().is(123l);
-		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Bar").assertLong().doesNotExist();
-		checkFooClient().build().get("/echo").header(BasicDateHeader.of("Foo",CALENDAR)).run().getResponseHeader("Foo").assertDate().exists();
-		checkFooClient().build().get("/echo").header(BasicDateHeader.of("Foo",CALENDAR)).run().getResponseHeader("Bar").assertDate().doesNotExist();
+		checkFooClient().build().get("/echo").header("Foo","bar").run().getResponseHeader("Foo").assertValue().is("bar");
+		checkFooClient().build().get("/echo").header("Foo","bar").run().getResponseHeader("Bar").assertValue().doesNotExist();
+		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Foo").assertValue().asInteger().is(123);
+		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Bar").assertValue().doesNotExist();
+		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Foo").assertValue().asLong().is(123l);
+		checkFooClient().build().get("/echo").header("Foo","123").run().getResponseHeader("Bar").assertValue().asLong().doesNotExist();
+		checkFooClient().build().get("/echo").header(BasicDateHeader.of("Foo",CALENDAR)).run().getResponseHeader("Foo").assertValue().asDate().exists();
+		checkFooClient().build().get("/echo").header(BasicDateHeader.of("Foo",CALENDAR)).run().getResponseHeader("Bar").assertValue().asDate().doesNotExist();
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java
index 36686b4..14ab963 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java
@@ -194,18 +194,18 @@ public class RestClient_Response_Test {
 		r.addHeader(BasicStringHeader.of("Foo","qux"));
 		assertEquals(3, r.getHeaders("Foo").length);
 		assertEquals(0, r.getHeaders("Bar").length);
-		r.getFirstHeader("Foo").assertString().is("bar");
+		r.getFirstHeader("Foo").assertValue().is("bar");
 		assertFalse(r.getFirstHeader("Bar").exists());
-		r.getLastHeader("Foo").assertString().is("qux");
+		r.getLastHeader("Foo").assertValue().is("qux");
 		assertFalse(r.getLastHeader("Bar").exists());
 
 		r.setHeaders(new Header[]{BasicHeader.of("Foo", "quux")});
-		r.getFirstHeader("Foo").assertString().is("quux");
-		r.getLastHeader("Foo").assertString().is("quux");
+		r.getFirstHeader("Foo").assertValue().is("quux");
+		r.getLastHeader("Foo").assertValue().is("quux");
 
 		r.removeHeader(BasicHeader.of("Foo","bar"));
-		r.getFirstHeader("Foo").assertString().is("quux");
-		r.getLastHeader("Foo").assertString().is("quux");
+		r.getFirstHeader("Foo").assertValue().is("quux");
+		r.getLastHeader("Foo").assertValue().is("quux");
 
 		HeaderIterator i = r.headerIterator();
 		assertEquals("quux", i.nextHeader().getValue());
@@ -217,7 +217,7 @@ public class RestClient_Response_Test {
 		assertFalse(r.getFirstHeader("Foo").exists());
 
 		r.setHeader(BasicHeader.of("Foo","quuux"));
-		r.getResponseHeader("Foo").assertString().is("quuux");
+		r.getResponseHeader("Foo").assertValue().is("quuux");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
@@ -234,14 +234,14 @@ public class RestClient_Response_Test {
 
 	@Test
 	public void d01_response_assertBody() throws Exception {
-		client(D.class).build().post("/bean",bean).run().assertBody(ABean.class).asJson().is("{f:1}");
+		client(D.class).build().post("/bean",bean).run().assertBody().asType(ABean.class).asJson().is("{f:1}");
 	}
 
 	@Test
 	public void d02_response_setEntity() throws Exception {
 		RestResponse x = client(D.class).build().post("/bean",bean).run();
 		x.setEntity(new StringEntity("{f:2}"));
-		x.assertBody(ABean.class).asJson().is("{f:2}");
+		x.assertBody().asType(ABean.class).asJson().is("{f:2}");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------