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 2020/11/16 16:51:58 UTC

[juneau] branch master updated: Better reusability of MockRestClient.

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 c0457be  Better reusability of MockRestClient.
c0457be is described below

commit c0457be5cda07e04864523a13d224110a0ff0d19
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Mon Nov 16 11:51:52 2020 -0500

    Better reusability of MockRestClient.
---
 .../java/org/apache/juneau/PropertyStoreTest.java  |  1 +
 .../remote/Remote_RemoteMethodAnnotation_Test.java |  7 +---
 .../main/java/org/apache/juneau/PropertyStore.java |  2 +
 .../org/apache/juneau/assertions/Assertions.java   | 16 ++++++++
 .../client/RestClient_Config_Context_Test.java     |  6 ++-
 .../client/RestClient_Config_RestClient_Test.java  | 45 ++++++++++++++++----
 .../rest/client/RestClient_Marshalls_Test.java     |  7 ++--
 .../org/apache/juneau/rest/client/RestClient.java  | 48 +++++++++++++++++++++-
 .../juneau/rest/client/RestClientBuilder.java      | 10 +++--
 .../org/apache/juneau/rest/client/RestRequest.java | 16 ++++++++
 .../juneau/rest/client/RestResponseBody.java       | 10 +++--
 .../rest/mock/MockHttpClientConnectionManager.java | 13 +++++-
 .../apache/juneau/rest/mock/MockRestClient.java    |  7 +++-
 .../juneau/rest/mock/MockRestClientBuilder.java    |  6 +--
 14 files changed, 157 insertions(+), 37 deletions(-)

diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/PropertyStoreTest.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/PropertyStoreTest.java
index 3611052..f50ca4c 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/PropertyStoreTest.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/PropertyStoreTest.java
@@ -1564,6 +1564,7 @@ public class PropertyStoreTest {
 	public void testSet() {
 		PropertyStoreBuilder b = PropertyStore.create();
 		b.set(OMap.of("A.foo", "bar"));
+		b.clear();
 		b.set(OMap.of("A.baz", "qux"));
 		b.add(null);
 		assertObject(b.build()).json().is("{A:{baz:'qux'}}");
diff --git a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/remote/Remote_RemoteMethodAnnotation_Test.java b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/remote/Remote_RemoteMethodAnnotation_Test.java
index 0f0ceac..920d4e3 100644
--- a/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/remote/Remote_RemoteMethodAnnotation_Test.java
+++ b/juneau-core/juneau-core-utest/src/test/java/org/apache/juneau/http/remote/Remote_RemoteMethodAnnotation_Test.java
@@ -23,7 +23,6 @@ import org.apache.juneau.http.annotation.Body;
 import org.apache.juneau.http.annotation.Response;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.client.*;
 import org.apache.juneau.rest.config.*;
 import org.apache.juneau.rest.mock.*;
 import org.junit.*;
@@ -238,7 +237,7 @@ public class Remote_RemoteMethodAnnotation_Test {
 
 	@Test
 	public void d01_returnTypes_partSerialization() throws Exception {
-		D1 x = client(D.class).openApi().build().getRemote(D1.class);
+		D1 x = MockRestClient.create(D.class).openApi().build().getRemote(D1.class);
 		assertEquals("foo",x.postX1("foo"));
 		assertEquals("foo",IOUtils.read(x.postX2("foo").getEntity().getContent()));
 		assertEquals("foo",IOUtils.read(x.postX3("foo")));
@@ -257,10 +256,6 @@ public class Remote_RemoteMethodAnnotation_Test {
 	// Helper methods.
 	//------------------------------------------------------------------------------------------------------------------
 
-	private static RestClientBuilder client(Class<?> c) {
-		return MockRestClient.create(c).simpleJson();
-	}
-
 	private static <T> T remote(Class<?> rest, Class<T> t) {
 		return MockRestClient.build(rest).getRemote(t);
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/PropertyStore.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/PropertyStore.java
index f9218d9..6c1f025 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/PropertyStore.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/PropertyStore.java
@@ -20,6 +20,7 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.PropertyStoreBuilder.*;
+import org.apache.juneau.assertions.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
@@ -565,6 +566,7 @@ public final class PropertyStore {
 	 * @return A new property instance.
 	 */
 	public <T> T getInstanceProperty(String key, Object outer, Class<T> type, Object def, ResourceResolver resolver, Object...args) {
+		Assertions.assertArgNotNull("type", type);
 		Property p = findProperty(key);
 		if (p != null)
 			return p.asInstance(outer, type, resolver, args);
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 f18224e..71d751d 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
@@ -16,6 +16,7 @@ import java.io.*;
 import java.time.*;
 import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
@@ -288,4 +289,19 @@ public class Assertions {
 	public static StringAssertion assertReader(Reader r) throws IOException {
 		return new StringAssertion(r == null ? null : IOUtils.read(r));
 	}
+
+	/**
+	 * Throws an {@link IllegalArgumentException} if the specified argument is <jk>null</jk>.
+	 *
+	 * @param <T> The argument data type.
+	 * @param arg The argument name.
+	 * @param o The object to check.
+	 * @return The same argument.
+	 * @throws IllegalArgumentException Constructed exception.
+	 */
+	public static <T> T assertArgNotNull(String arg, T o) throws IllegalArgumentException {
+		if (o == null)
+			throw new BasicIllegalArgumentException("Argument ''{0}'' cannot be null", arg);
+		return o;
+	}
 }
diff --git a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_Context_Test.java b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_Context_Test.java
index 94b32b6..0e77869 100644
--- a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_Context_Test.java
+++ b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_Context_Test.java
@@ -194,13 +194,15 @@ public class RestClient_Config_Context_Test {
 
 	@Test
 	public void a11_set() throws Exception {
-		client(null).set(
+		client(null)
+			.add(
 				AMap.of(
 					JSON_simpleMode,true,
 					WSERIALIZER_quoteChar,"'",
 					MOCKRESTCLIENT_restBean,A.class
 				)
-			).json().build().post("/echoBody",new A11()).run().assertBody().is("{foo:1}")
+			)
+			.json().build().post("/echoBody",new A11()).mediaType("text/json").run().assertBody().is("{foo:1}")
 		;
 	}
 
diff --git a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
index 719e56d..6cc813e 100644
--- a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
+++ b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Config_RestClient_Test.java
@@ -365,15 +365,23 @@ public class RestClient_Config_RestClient_Test {
 
 	@Test
 	public void a09_marshalls() throws Exception {
-		RestClient x = MockRestClient.create(A.class).marshalls(Xml.DEFAULT,Json.DEFAULT).build();
+		final RestClient x = MockRestClient.create(A.class).marshalls(Xml.DEFAULT,Json.DEFAULT).build();
 
-		x.post("/echoBody",bean).run().assertBody().is("{f:1}");
+		assertThrown(()->x.post("/echoBody",bean).run()).contains("Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
+
+		assertThrown(()->x.post("/echoBody",bean).contentType("text/json").run().getBody().as(ABean.class)).contains("Content-Type not specified in response header.  Cannot find appropriate parser.");
 
 		ABean b = x.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
 		assertObject(b).sameAs(bean);
 
+		b = x.post("/echoBody",bean).mediaType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
+		assertObject(b).sameAs(bean);
+
 		b = x.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
 		assertObject(b).sameAs(bean);
+
+		b = x.post("/echoBody",bean).mediaType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
+		assertObject(b).sameAs(bean);
 	}
 
 	@Test
@@ -395,19 +403,40 @@ public class RestClient_Config_RestClient_Test {
 	@Test
 	public void a11_serializers_parsers() throws Exception {
 		@SuppressWarnings("unchecked")
-		RestClient x = MockRestClient.create(A.class).serializers(XmlSerializer.class,JsonSerializer.class).parsers(XmlParser.class,JsonParser.class).build();
+		final RestClient x = MockRestClient.create(A.class).serializers(XmlSerializer.class,JsonSerializer.class).parsers(XmlParser.class,JsonParser.class).build();
+
+		assertThrown(()->x.post("/echoBody",bean).run()).contains("Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
+
+		assertThrown(()->x.post("/echoBody",bean).contentType("text/json").run().getBody().as(ABean.class)).contains("Content-Type not specified in response header.  Cannot find appropriate parser.");
 
-		x.post("/echoBody",bean).run().assertBody().is("{f:1}");
 		ABean b = x.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
 		assertObject(b).sameAs(bean);
+
+		b = x.post("/echoBody",bean).mediaType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
+		assertObject(b).sameAs(bean);
+
 		b = x.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
 		assertObject(b).sameAs(bean);
 
-		x = MockRestClient.create(A.class).serializers(XmlSerializer.DEFAULT,JsonSerializer.DEFAULT).parsers(XmlParser.DEFAULT,JsonParser.DEFAULT).build();
-		x.post("/echoBody",bean).run().assertBody().is("{f:1}");
-		b = x.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
+		b = x.post("/echoBody",bean).mediaType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
 		assertObject(b).sameAs(bean);
-		b = x.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
+
+		final RestClient x2 = MockRestClient.create(A.class).serializers(XmlSerializer.DEFAULT,JsonSerializer.DEFAULT).parsers(XmlParser.DEFAULT,JsonParser.DEFAULT).build();
+
+		assertThrown(()->x2.post("/echoBody",bean).run()).contains("Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
+
+		assertThrown(()->x2.post("/echoBody",bean).contentType("text/json").run().getBody().as(ABean.class)).contains("Content-Type not specified in response header.  Cannot find appropriate parser.");
+
+		b = x2.post("/echoBody",bean).accept("text/xml").contentType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
+		assertObject(b).sameAs(bean);
+
+		b = x2.post("/echoBody",bean).mediaType("text/xml").run().cacheBody().assertBody().is("<object><f>1</f></object>").getBody().as(ABean.class);
+		assertObject(b).sameAs(bean);
+
+		b = x2.post("/echoBody",bean).accept("text/json").contentType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
+		assertObject(b).sameAs(bean);
+
+		b = x2.post("/echoBody",bean).mediaType("text/json").run().cacheBody().assertBody().is("{\"f\":1}").getBody().as(ABean.class);
 		assertObject(b).sameAs(bean);
 	}
 
diff --git a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Marshalls_Test.java b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Marshalls_Test.java
index 1d72279..6f15000 100644
--- a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Marshalls_Test.java
+++ b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Marshalls_Test.java
@@ -12,6 +12,7 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.rest.client;
 
+import static org.apache.juneau.assertions.Assertions.*;
 import static org.junit.Assert.*;
 import static org.junit.runners.MethodSorters.*;
 
@@ -132,8 +133,7 @@ public class RestClient_Marshalls_Test {
 		x.post("/a01",bean).header("Accept","application/x-www-form-urlencoded").header("Content-Type","application/x-www-form-urlencoded").header("X-Accept","application/x-www-form-urlencoded").header("X-Content-Type","application/x-www-form-urlencoded").run().assertCode().is(200).getBody().as(Bean.class).check();
 		x.post("/a01",bean).header("Accept","text/openapi").header("Content-Type","text/openapi").header("X-Accept","text/openapi").header("X-Content-Type","text/openapi").run().assertCode().is(200).getBody().as(Bean.class).check();
 
-		// Bean will be serialized using toString().
-		x.post("/a01",bean).header("X-Accept","nil").header("X-Content-Type","text/plain").run().assertCode().is(200).getBody().as(Bean.class).check();
+		assertThrown(()->x.post("/a01",bean).run()).contains("Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
@@ -152,8 +152,7 @@ public class RestClient_Marshalls_Test {
 		x.post("/a01",bean).header("Accept","application/x-www-form-urlencoded").header("Content-Type","application/x-www-form-urlencoded").header("X-Accept","application/x-www-form-urlencoded").header("X-Content-Type","application/x-www-form-urlencoded").run().assertCode().is(200).getBody().as(Bean.class).check();
 		x.post("/a01",bean).header("Accept","text/openapi").header("Content-Type","text/openapi").header("X-Accept","text/openapi").header("X-Content-Type","text/openapi").run().assertCode().is(200).getBody().as(Bean.class).check();
 
-		// Default.
-		x.post("/a01",bean).header("X-Accept","nil").header("X-Content-Type","text/plain").run().assertCode().is(200).getBody().as(Bean.class).check();
+		assertThrown(()->x.post("/a01",bean).run()).contains("Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
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 ba6e170..dcf56d8 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
@@ -1093,6 +1093,31 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 	public static final String RESTCLIENT_callHandler = PREFIX + "callHandler.o";
 
 	/**
+	 * Configuration property:  Connection manager
+	 *
+	 * <h5 class='section'>Property:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li><b>ID:</b>  {@link org.apache.juneau.rest.client.RestClient#RESTCLIENT_connectionManager RESTCLIENT_connectionManager}
+	 * 	<li><b>Name:</b>  <js>"RestClient.connectionManager.o"</js>
+	 * 	<li><b>System property:</b>  <c>RestClient.connectionManager</c>
+	 * 	<li><b>Data type:</b>
+	 * 	<ul>
+	 * 		<li><b>Data type:</b>  {@link org.apache.http.conn.HttpClientConnectionManager}</c>
+	 * 	</ul>
+	 * 	<li><b>Default:</b>  Value returned by {@link org.apache.juneau.rest.client.RestClientBuilder#createConnectionManager()}
+	 * 	<li><b>Methods:</b>
+	 * 		<ul>
+	 * 			<li class='jm'>{@link org.apache.juneau.rest.client.RestClientBuilder#connectionManager(HttpClientConnectionManager)}
+	 * 		</ul>
+	 * </ul>
+	 *
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * Allows you to override the connection manager used by the HTTP client.
+	 */
+	public static final String RESTCLIENT_connectionManager = PREFIX + "connectionManager.o";
+
+	/**
 	 * Configuration property:  Console print stream.
 	 *
 	 * <h5 class='section'>Property:</h5>
@@ -1953,6 +1978,7 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 	private final HeaderSupplier headers;
 	private final NameValuePairSupplier query, formData;
 	final CloseableHttpClient httpClient;
+	private final HttpClientConnectionManager connectionManager;
 	private final boolean keepHttpClientOpen, leakDetection;
 	private final UrlEncodingSerializer urlEncodingSerializer;  // Used for form posts only.
 	private final HttpPartSerializer partSerializer;
@@ -2011,6 +2037,7 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 	protected RestClient(PropertyStore ps) {
 		super(ps);
 		this.httpClient = getInstanceProperty(RESTCLIENT_httpClient, CloseableHttpClient.class);
+		this.connectionManager = getInstanceProperty(RESTCLIENT_connectionManager, HttpClientConnectionManager.class);
 		this.keepHttpClientOpen = getBooleanProperty(RESTCLIENT_keepHttpClientOpen);
 		this.errorCodes = getInstanceProperty(RESTCLIENT_errorCodes, Predicate.class, ERROR_CODES_DEFAULT);
 		this.executorServiceShutdownOnClose = getBooleanProperty(RESTCLIENT_executorServiceShutdownOnClose);
@@ -3452,6 +3479,15 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 	}
 
 	/**
+	 * Returns the connection manager if one was specified in the client builder.
+	 *
+	 * @return The connection manager.  May be <jk>null</jk>.
+	 */
+	public HttpClientConnectionManager getHttpClientConnectionManager() {
+		return connectionManager;
+	}
+
+	/**
 	 * Executes HTTP request using the default context.
 	 *
 	 * <ul class='notes'>
@@ -3709,7 +3745,11 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 				return s;
 		}
 		List<Serializer> l = serializers.getSerializers();
-		return l.size() == 1 ? l.get(0) : null;
+		return (l.size() == 1 ? l.get(0) : null);
+	}
+
+	boolean hasSerializers() {
+		return ! serializers.getSerializers().isEmpty();
 	}
 
 	/*
@@ -3726,7 +3766,11 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
 				return p;
 		}
 		List<Parser> l = parsers.getParsers();
-		return l.size() == 1 ? l.get(0) : null;
+		return (l.size() == 1 ? l.get(0) : null);
+	}
+
+	boolean hasParsers() {
+		return ! parsers.getParsers().isEmpty();
 	}
 
 	@SuppressWarnings("unchecked")
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
index 5474d94..fb4a1de 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
@@ -96,7 +96,6 @@ public class RestClientBuilder extends BeanContextBuilder {
 
 	private HttpClientBuilder httpClientBuilder;
 	private CloseableHttpClient httpClient;
-	private HttpClientConnectionManager httpClientConnectionManager;
 	private boolean pooled;
 
 	/**
@@ -799,11 +798,14 @@ public class RestClientBuilder extends BeanContextBuilder {
 	 * @return The HTTP client to use.
 	 */
 	protected CloseableHttpClient createHttpClient() {
+		Object cm = peek(RESTCLIENT_connectionManager);
 		// Don't call createConnectionManager() if RestClient.setConnectionManager() was called.
-		if (httpClientConnectionManager == null)
+		if (cm == null)
 			httpClientBuilder.setConnectionManager(createConnectionManager());
+		else if (cm instanceof HttpClientConnectionManager)
+			httpClientBuilder.setConnectionManager((HttpClientConnectionManager)cm);
 		else
-			httpClientBuilder.setConnectionManager(httpClientConnectionManager);
+			throw new RuntimeException("Invalid type for RESTCLIENT_connectionManager: " + cm.getClass().getName());
 		return httpClientBuilder.build();
 	}
 
@@ -5284,7 +5286,7 @@ public class RestClientBuilder extends BeanContextBuilder {
 	 */
 	@FluentSetter
 	public RestClientBuilder connectionManager(HttpClientConnectionManager connManager) {
-		this.httpClientConnectionManager = connManager;
+		set(RESTCLIENT_connectionManager, connManager);
 		httpClientBuilder.setConnectionManager(connManager);
 		return this;
 	}
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index 73b01b2..c6bc151 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -2452,6 +2452,17 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
 	}
 
 	/**
+	 * Shortcut for setting the <c>Accept</c> and <c>Content-Type</c> headers on a request.
+	 *
+	 * @param value The new header values.
+	 * @return This object (for method chaining).
+	 * @throws RestCallException Invalid input.
+	 */
+	public RestRequest mediaType(Object value) throws RestCallException {
+		return header("Accept", value).header("Content-Type", value);
+	}
+
+	/**
 	 * Sets the value for the <c>Content-Encoding</c> request header.
 	 *
 	 * <p>
@@ -2868,6 +2879,11 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
 				else if (serializer != null)
 					entity = SerializedHttpEntity.of(input2, serializer).schema(requestBodySchema).contentType(contentType);
 				else {
+					if (client.hasSerializers()) {
+						if (contentType == null)
+							throw new RestCallException(null, null, "Content-Type not specified on request.  Cannot match correct serializer.  Use contentType(String) or mediaType(String) to specify transport language.");
+						throw new RestCallException(null, null, "No matching serializer for media type ''{0}''", contentType);
+					}
 					if (input2 == null)
 						input2 = "";
 					entity = new StringEntity(BeanContext.DEFAULT.getClassMetaForObject(input2).toString(input2), getRequestContentType(TEXT_PLAIN));
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponseBody.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponseBody.java
index 8e97735..8162f55 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponseBody.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestResponseBody.java
@@ -816,10 +816,12 @@ public class RestResponseBody implements HttpEntity {
 			if (type.hasInputStreamMutater())
 				return type.getInputStreamMutater().mutate(asInputStream());
 
-			throw new ParseException(
-				"Unsupported media-type in request header ''Content-Type'': ''{0}''",
-				response.getStringHeader("Content-Type")
-			);
+			ct = response.getStringHeader("Content-Type");
+
+			if (ct == null && client.hasParsers())
+				throw new ParseException("Content-Type not specified in response header.  Cannot find appropriate parser.");
+
+			throw new ParseException("Unsupported media-type in request header ''Content-Type'': ''{0}''", ct);
 
 		} catch (ParseException | IOException e) {
 			response.close();
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockHttpClientConnectionManager.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockHttpClientConnectionManager.java
index c2b8781..0001fbb 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockHttpClientConnectionManager.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockHttpClientConnectionManager.java
@@ -28,7 +28,7 @@ import org.apache.http.protocol.*;
  *
  * This implementation is NOT thread safe.
  */
-class MockHttpClientConnectionManager implements HttpClientConnectionManager {
+final class MockHttpClientConnectionManager implements HttpClientConnectionManager {
 
 	private ConnectionRequest cr;
 
@@ -75,4 +75,15 @@ class MockHttpClientConnectionManager implements HttpClientConnectionManager {
 
 	@Override /* HttpClientConnectionManager */
 	public void shutdown() {}
+
+	@Override /* Object */
+	public int hashCode() {
+		return MockHttpClientConnectionManager.class.hashCode();
+	}
+
+	@Override /* Object */
+	public boolean equals(Object o) {
+		// All MockHttpClientConnectionManagers are considered equal.
+		return o != null && o instanceof MockHttpClientConnectionManager;
+	}
 }
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
index 083687c..71df441 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
@@ -25,6 +25,7 @@ import javax.servlet.http.*;
 
 import org.apache.http.*;
 import org.apache.http.client.methods.*;
+import org.apache.http.conn.*;
 import org.apache.http.entity.*;
 import org.apache.http.message.*;
 import org.apache.juneau.*;
@@ -223,7 +224,6 @@ public class MockRestClient extends RestClient implements HttpClientConnection {
 		MOCKRESTCLIENT_restBeanCtx = PREFIX + "restBeanCtx.o",
 		MOCKRESTCLIENT_servletPath = PREFIX + "servletPath.s",
 		MOCKRESTCLIENT_contextPath = PREFIX + "contextPath.s",
-		MOCKRESTCLIENT_mockHttpClientConnectionManager = PREFIX + "mockHttpClientConnectionManager.o",
 		MOCKRESTCLIENT_pathVars = PREFIX + "pathVars.oms";
 
 
@@ -257,7 +257,10 @@ public class MockRestClient extends RestClient implements HttpClientConnection {
 		this.contextPath = getStringProperty(MOCKRESTCLIENT_contextPath, "");
 		this.servletPath = getStringProperty(MOCKRESTCLIENT_servletPath, "");
 		this.pathVars = getMapProperty(MOCKRESTCLIENT_pathVars, String.class);
-		getInstanceProperty(MOCKRESTCLIENT_mockHttpClientConnectionManager, MockHttpClientConnectionManager.class).init(this);
+
+		HttpClientConnectionManager ccm = getHttpClientConnectionManager();
+		if (ccm instanceof MockHttpClientConnectionManager)
+			((MockHttpClientConnectionManager)ccm).init(this);
 	}
 
 	private static PropertyStore preInit(PropertyStore ps) {
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java
index 509d16a..fb7e308 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java
@@ -72,6 +72,7 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	 */
 	protected MockRestClientBuilder(PropertyStore ps) {
 		super(ps);
+		connectionManager(new MockHttpClientConnectionManager());
 	}
 
 	/**
@@ -81,7 +82,7 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	 * Provided so that this class can be easily subclassed.
 	 */
 	protected MockRestClientBuilder() {
-		super(null);
+		this(null);
 	}
 
 	/**
@@ -214,9 +215,6 @@ public class MockRestClientBuilder extends RestClientBuilder {
 
 	@Override /* ContextBuilder */
 	public <T extends Context> T build(Class<T> c) {
-		MockHttpClientConnectionManager cm = new MockHttpClientConnectionManager();
-		set(MOCKRESTCLIENT_mockHttpClientConnectionManager, cm);
-		connectionManager(cm);
 		return super.build(c);
 	}