Repository: square/retrofit
Issue: Switch each converter's tests from unit to integration

We should be testing these as installed in a `Retrofit` instance with `MockWebServer`.


Issue comments:

Pull request review comments:
This really should be needed, but for some reason the older Okio dependency from Wire's runtime is beating out the newer version which is specified by OkHttp (itself a transitive dependency of Retrofit).

Nit: comma

Bizarre.

(Probably worthwhile to grep and make sure 100% of your projects are consistent.)

`while (jsonReader.hasNext())`

... Unless you're going for symmetry with the other libraries!

The converter factory also lets you fast fail here.

Proto encoding is weird.

Can we be more specific than RuntimeException in what we their here? Moshi's JsonDataException seems like a usability win.

Yep. We were erroneously fast-tracking an empty body to `null` in 1.x which broke this use case.

:+1: 

who writes code like this?

moved file or mistake?

bothWays ?

win

nested class -> top-level class

Hah. IDEA probably generated this craziness.

Follow-up. There's also problems with Wire here.


Patch that resolved the issue:
diff --git a/pom.xml b/pom.xml
index eadd587503..f9adffe1db 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,7 @@
     <gson.version>2.3.1</gson.version>
     <protobuf.version>2.5.0</protobuf.version>
     <jackson.version>2.4.3</jackson.version>
-    <wire.version>1.5.2</wire.version>
+    <wire.version>1.7.0</wire.version>
     <simplexml.version>2.7.1</simplexml.version>
     <moshi.version>0.9.0</moshi.version>
 
diff --git a/retrofit-converters/gson/pom.xml b/retrofit-converters/gson/pom.xml
index e353bb3253..0cb591aced 100644
--- a/retrofit-converters/gson/pom.xml
+++ b/retrofit-converters/gson/pom.xml
@@ -30,13 +30,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/retrofit-converters/jackson/pom.xml b/retrofit-converters/jackson/pom.xml
index 06c5a2992e..18d0340b4c 100644
--- a/retrofit-converters/jackson/pom.xml
+++ b/retrofit-converters/jackson/pom.xml
@@ -30,13 +30,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/retrofit-converters/moshi/pom.xml b/retrofit-converters/moshi/pom.xml
index ad40686622..627c62b992 100644
--- a/retrofit-converters/moshi/pom.xml
+++ b/retrofit-converters/moshi/pom.xml
@@ -30,13 +30,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/retrofit-converters/moshi/src/main/java/retrofit/MoshiConverter.java b/retrofit-converters/moshi/src/main/java/retrofit/MoshiConverter.java
index 6dcaa686e5..b3f803ad4f 100644
--- a/retrofit-converters/moshi/src/main/java/retrofit/MoshiConverter.java
+++ b/retrofit-converters/moshi/src/main/java/retrofit/MoshiConverter.java
@@ -38,7 +38,7 @@ public MoshiConverter() {
   public MoshiConverter(Moshi moshi) {
     if (moshi == null) throw new NullPointerException("moshi == null");
     this.moshi = moshi;
-    this.mediaType = MediaType.parse("application/json; charset=utf-8");
+    this.mediaType = MediaType.parse("application/json; charset=UTF-8");
   }
 
   @Override public Object fromBody(ResponseBody body, Type type) throws IOException {
diff --git a/retrofit-converters/protobuf/pom.xml b/retrofit-converters/protobuf/pom.xml
index d2808b7eac..c635c4854a 100644
--- a/retrofit-converters/protobuf/pom.xml
+++ b/retrofit-converters/protobuf/pom.xml
@@ -30,13 +30,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/retrofit-converters/simplexml/pom.xml b/retrofit-converters/simplexml/pom.xml
index 4a83d0a787..1eba174cb4 100644
--- a/retrofit-converters/simplexml/pom.xml
+++ b/retrofit-converters/simplexml/pom.xml
@@ -30,13 +30,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/retrofit-converters/simplexml/src/main/java/retrofit/SimpleXmlConverter.java b/retrofit-converters/simplexml/src/main/java/retrofit/SimpleXmlConverter.java
index 36ba06f86f..c737dba241 100644
--- a/retrofit-converters/simplexml/src/main/java/retrofit/SimpleXmlConverter.java
+++ b/retrofit-converters/simplexml/src/main/java/retrofit/SimpleXmlConverter.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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 retrofit;
 
 import com.squareup.okhttp.MediaType;
@@ -46,8 +61,12 @@ public SimpleXmlConverter(Serializer serializer, boolean strict) {
   @Override public Object fromBody(ResponseBody body, Type type) throws IOException {
     InputStream is = body.byteStream();
     try {
-      return serializer.read((Class<?>) type, is, strict);
-    } catch (IOException e) {
+      Object read = serializer.read((Class<?>) type, is, strict);
+      if (read == null) {
+        throw new IllegalStateException("Could not deserialize body as " + type);
+      }
+      return read;
+    } catch (RuntimeException | IOException e) {
       throw e;
     } catch (Exception e) {
       throw new RuntimeException(e);
@@ -60,17 +79,15 @@ public SimpleXmlConverter(Serializer serializer, boolean strict) {
   }
 
   @Override public RequestBody toBody(Object source, Type type) {
-    byte[] bytes;
+    Buffer buffer = new Buffer();
     try {
-      Buffer buffer = new Buffer();
       OutputStreamWriter osw = new OutputStreamWriter(buffer.outputStream(), CHARSET);
       serializer.write(source, osw);
       osw.flush();
-      bytes = buffer.readByteArray();
     } catch (Exception e) {
-      throw new AssertionError(e);
+      throw new RuntimeException(e);
     }
-    return RequestBody.create(MEDIA_TYPE, bytes);
+    return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
   }
 
   public boolean isStrict() {
diff --git a/retrofit-converters/wire/pom.xml b/retrofit-converters/wire/pom.xml
index f56ef5fcbe..d2bf06f14c 100644
--- a/retrofit-converters/wire/pom.xml
+++ b/retrofit-converters/wire/pom.xml
@@ -26,6 +26,13 @@
     <dependency>
       <groupId>com.squareup.wire</groupId>
       <artifactId>wire-runtime</artifactId>
+      <exclusions>
+        <!-- Make sure OkHttp's transitive version wins (itself transitive from Retrofit). -->
+        <exclusion>
+          <groupId>com.squareup.okio</groupId>
+          <artifactId>okio</artifactId>
+        </exclusion>
+      </exclusions>
     </dependency>
 
     <dependency>
@@ -34,13 +41,13 @@
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.assertj</groupId>
-      <artifactId>assertj-core</artifactId>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/retrofit/src/main/java/retrofit/RequestBuilder.java b/retrofit/src/main/java/retrofit/RequestBuilder.java
index 55951df769..cf06d4b18d 100644
--- a/retrofit/src/main/java/retrofit/RequestBuilder.java
+++ b/retrofit/src/main/java/retrofit/RequestBuilder.java
@@ -26,6 +26,7 @@
 import java.io.UnsupportedEncodingException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Array;
+import java.lang.reflect.Type;
 import java.net.URLEncoder;
 import java.util.Map;
 import okio.BufferedSink;
@@ -47,6 +48,7 @@ final class RequestBuilder {
   private final Annotation[] paramAnnotations;
   private final String requestMethod;
   private final boolean requestHasBody;
+  private final Type requestType;
   private final HttpUrl.Builder urlBuilder;
 
   private MultipartBuilder multipartBuilder;
@@ -63,6 +65,7 @@ final class RequestBuilder {
     paramAnnotations = methodInfo.requestParamAnnotations;
     requestMethod = methodInfo.requestMethod;
     requestHasBody = methodInfo.requestHasBody;
+    requestType = methodInfo.requestType;
 
     if (methodInfo.headers != null) {
       headers = methodInfo.headers.newBuilder();
@@ -317,10 +320,11 @@ void setArguments(Object[] args) {
         if (value == null) {
           throw new IllegalArgumentException("Body parameter value must not be null.");
         }
-        if (value instanceof RequestBody) {
+        if (requestType == RequestBody.class
+            || (requestType == Object.class && value instanceof RequestBody)) {
           body = (RequestBody) value;
         } else {
-          body = converter.toBody(value, value.getClass());
+          body = converter.toBody(value, requestType);
         }
       } else {
         throw new IllegalArgumentException(


Test patch:
diff --git a/retrofit-converters/gson/src/test/java/retrofit/GsonConverterTest.java b/retrofit-converters/gson/src/test/java/retrofit/GsonConverterTest.java
index a4449757cf..a303727668 100644
--- a/retrofit-converters/gson/src/test/java/retrofit/GsonConverterTest.java
+++ b/retrofit-converters/gson/src/test/java/retrofit/GsonConverterTest.java
@@ -1,36 +1,47 @@
-// Copyright 2015 Square, Inc.
+/*
+ * Copyright (C) 2015 Square, Inc.
+ *
+ * Licensed 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 retrofit;
 
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.ResponseBody;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
 import java.io.IOException;
-import java.lang.reflect.Type;
-import okio.Buffer;
-import org.assertj.core.api.AbstractCharSequenceAssert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import retrofit.http.Body;
+import retrofit.http.POST;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
 
 public final class GsonConverterTest {
-  private Converter converter;
-
-  interface Example {
+  interface AnInterface {
     String getName();
   }
 
-  class Impl implements Example {
+  static class AnImplementation implements AnInterface {
     private final String theName;
 
-    Impl(String name) {
+    AnImplementation(String name) {
       theName = name;
     }
 
@@ -39,40 +50,75 @@ class Impl implements Example {
     }
   }
 
+  static class AnInterfaceAdapter extends TypeAdapter<AnInterface> {
+    @Override public void write(JsonWriter jsonWriter, AnInterface anInterface) throws IOException {
+      jsonWriter.beginObject();
+      jsonWriter.name("name").value(anInterface.getName());
+      jsonWriter.endObject();
+    }
+
+    @Override public AnInterface read(JsonReader jsonReader) throws IOException {
+      jsonReader.beginObject();
+
+      String name = null;
+      while (jsonReader.peek() != JsonToken.END_OBJECT) {
+        switch (jsonReader.nextName()) {
+          case "name":
+            name = jsonReader.nextString();
+            break;
+        }
+      }
+
+      jsonReader.endObject();
+      return new AnImplementation(name);
+    }
+  }
+
+  interface Service {
+    @POST("/") Call<AnImplementation> anImplementation(@Body AnImplementation impl);
+    @POST("/") Call<AnInterface> anInterface(@Body AnInterface impl);
+  }
+
+  @Rule public final MockWebServerRule server = new MockWebServerRule();
+
+  private Service service;
+
   @Before public void setUp() {
     Gson gson = new GsonBuilder()
-        .registerTypeAdapter(Example.class, new JsonSerializer<Example>() {
-          @Override public JsonElement serialize(Example example, Type type,
-              JsonSerializationContext json) {
-            JsonObject object = new JsonObject();
-            object.addProperty("name", example.getName());
-            return object;
-          }
-        })
+        .registerTypeAdapter(AnInterface.class, new AnInterfaceAdapter())
         .create();
-    converter = new GsonConverter(gson);
+    Converter converter = new GsonConverter(gson);
+    Retrofit retrofit = new Retrofit.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(converter)
+        .build();
+    service = retrofit.create(Service.class);
   }
 
-  @Test public void serialization() throws IOException {
-    RequestBody body = converter.toBody(new Impl("value"), Impl.class);
-    assertBody(body).isEqualTo("{\"theName\":\"value\"}");
-  }
+  @Test public void anInterface() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("{\"name\":\"value\"}"));
 
-  @Test public void serializationTypeUsed() throws IOException {
-    RequestBody body = converter.toBody(new Impl("value"), Example.class);
-    assertBody(body).isEqualTo("{\"name\":\"value\"}");
-  }
+    Call<AnInterface> call = service.anInterface(new AnImplementation("value"));
+    Response<AnInterface> response = call.execute();
+    AnInterface body = response.body();
+    assertThat(body.getName()).isEqualTo("value");
 
-  @Test public void deserialization() throws IOException {
-    ResponseBody body =
-        ResponseBody.create(MediaType.parse("text/plain"), "{\"theName\":\"value\"}");
-    Impl impl = (Impl) converter.fromBody(body, Impl.class);
-    assertEquals("value", impl.getName());
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readUtf8()).isEqualTo("{\"name\":\"value\"}");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
   }
 
-  private static AbstractCharSequenceAssert<?, String> assertBody(RequestBody body) throws IOException {
-    Buffer buffer = new Buffer();
-    body.writeTo(buffer);
-    return assertThat(buffer.readUtf8());
+  @Test public void anImplementation() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("{\"theName\":\"value\"}"));
+
+    Call<AnImplementation> call = service.anImplementation(new AnImplementation("value"));
+    Response<AnImplementation> response = call.execute();
+    AnImplementation body = response.body();
+    assertThat(body.theName).isEqualTo("value");
+
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readUtf8()).isEqualTo("{\"theName\":\"value\"}");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
+
   }
 }
diff --git a/retrofit-converters/jackson/src/test/java/retrofit/JacksonConverterTest.java b/retrofit-converters/jackson/src/test/java/retrofit/JacksonConverterTest.java
index 8df5529d9b..d552b7bc3d 100644
--- a/retrofit-converters/jackson/src/test/java/retrofit/JacksonConverterTest.java
+++ b/retrofit-converters/jackson/src/test/java/retrofit/JacksonConverterTest.java
@@ -1,89 +1,156 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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 retrofit;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.ResponseBody;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
 import java.io.IOException;
-import okio.Buffer;
-import org.assertj.core.api.AbstractCharSequenceAssert;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import retrofit.http.Body;
+import retrofit.http.POST;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class JacksonConverterTest {
-  private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
-  private static final MyObject OBJECT = new MyObject("hello world", 10);
-  private final String JSON = "{\"message\":\"hello world\",\"count\":10}";
+  interface AnInterface {
+    String getName();
+  }
 
-  private final JacksonConverter converter = new JacksonConverter();
+  static class AnImplementation implements AnInterface {
+    private String theName;
 
-  @Test public void serialize() throws Exception {
-    RequestBody body = converter.toBody(OBJECT, MyObject.class);
-    assertThat(body.contentType()).isEqualTo(MEDIA_TYPE);
-    assertBody(body).isEqualTo(JSON);
-  }
+    AnImplementation() {
+    }
+
+    AnImplementation(String name) {
+      theName = name;
+    }
 
-  @Test public void deserialize() throws Exception {
-    ResponseBody body = ResponseBody.create(MEDIA_TYPE, JSON);
-    MyObject result = (MyObject) converter.fromBody(body, MyObject.class);
-    assertThat(result).isEqualTo(OBJECT);
+    @Override public String getName() {
+      return theName;
+    }
   }
 
-  @Test public void deserializeWrongValue() throws Exception {
-    ResponseBody body = ResponseBody.create(MEDIA_TYPE, "{\"foo\":\"bar\"}");
-    try {
-      converter.fromBody(body, MyObject.class);
-    } catch (UnrecognizedPropertyException ignored) {
+  static class AnInterfaceSerializer extends StdSerializer<AnInterface> {
+    AnInterfaceSerializer() {
+      super(AnInterface.class);
+    }
+
+    @Override public void serialize(AnInterface anInterface, JsonGenerator jsonGenerator,
+        SerializerProvider serializerProvider) throws IOException {
+      jsonGenerator.writeStartObject();
+      jsonGenerator.writeFieldName("name");
+      jsonGenerator.writeString(anInterface.getName());
+      jsonGenerator.writeEndObject();
     }
   }
 
-  @Test public void deserializeWrongClass() throws Exception {
-    ResponseBody body = ResponseBody.create(MEDIA_TYPE, JSON);
-    try {
-      converter.fromBody(body, String.class);
-    } catch (JsonMappingException ignored) {
+  static class AnInterfaceDeserializer extends StdDeserializer<AnInterface> {
+    AnInterfaceDeserializer() {
+      super(AnInterface.class);
+    }
+
+    @Override public AnInterface deserialize(JsonParser jp, DeserializationContext ctxt)
+        throws IOException {
+      if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
+        throw new AssertionError("Expected start object.");
+      }
+
+      String name = null;
+
+      while (jp.nextToken() != JsonToken.END_OBJECT) {
+        switch (jp.getCurrentName()) {
+          case "name":
+            name = jp.getValueAsString();
+            break;
+        }
+      }
+
+      return new AnImplementation(name);
     }
   }
 
-  private static AbstractCharSequenceAssert<?, String> assertBody(RequestBody body) throws IOException {
-    Buffer buffer = new Buffer();
-    body.writeTo(buffer);
-    return assertThat(buffer.readUtf8());
+  interface Service {
+    @POST("/") Call<AnImplementation> anImplementation(@Body AnImplementation impl);
+    @POST("/") Call<AnInterface> anInterface(@Body AnInterface impl);
   }
 
-  static class MyObject {
-    private final String message;
-    private final int count;
+  @Rule public final MockWebServerRule server = new MockWebServerRule();
+
+  private Service service;
+
+  @Before public void setUp() {
+    SimpleModule module = new SimpleModule();
+    module.addSerializer(AnInterface.class, new AnInterfaceSerializer());
+    module.addDeserializer(AnInterface.class, new AnInterfaceDeserializer());
+    ObjectMapper mapper = new ObjectMapper();
+    mapper.registerModule(module);
+    mapper.configure(MapperFeature.AUTO_DETECT_GETTERS, false);
+    mapper.configure(MapperFeature.AUTO_DETECT_SETTERS, false);
+    mapper.configure(MapperFeature.AUTO_DETECT_IS_GETTERS, false);
+    mapper.setVisibilityChecker(mapper.getSerializationConfig()
+        .getDefaultVisibilityChecker()
+        .withFieldVisibility(JsonAutoDetect.Visibility.ANY));
+
+    Converter converter = new JacksonConverter(mapper);
+    Retrofit retrofit = new Retrofit.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(converter)
+        .build();
+    service = retrofit.create(Service.class);
+  }
 
-    public MyObject(@JsonProperty("message") String message, @JsonProperty("count") int count) {
-      this.message = message;
-      this.count = count;
-    }
+  @Test public void anInterface() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("{\"name\":\"value\"}"));
 
-    public String getMessage() {
-      return message;
-    }
+    Call<AnInterface> call = service.anInterface(new AnImplementation("value"));
+    Response<AnInterface> response = call.execute();
+    AnInterface body = response.body();
+    assertThat(body.getName()).isEqualTo("value");
 
-    public int getCount() {
-      return count;
-    }
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readUtf8()).isEqualTo("{\"name\":\"value\"}");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
+  }
 
-    @Override public boolean equals(Object o) {
-      if (this == o) return true;
-      if (o == null || getClass() != o.getClass()) return false;
+  @Test public void anImplementation() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("{\"theName\":\"value\"}"));
 
-      MyObject myObject = (MyObject) o;
-      return count == myObject.count
-          && !(message != null ? !message.equals(myObject.message) : myObject.message != null);
-    }
+    Call<AnImplementation> call = service.anImplementation(new AnImplementation("value"));
+    Response<AnImplementation> response = call.execute();
+    AnImplementation body = response.body();
+    assertThat(body.theName).isEqualTo("value");
 
-    @Override public int hashCode() {
-      int result = message != null ? message.hashCode() : 0;
-      result = 31 * result + count;
-      return result;
-    }
+    RecordedRequest request = server.takeRequest();
+    // TODO figure out how to get Jackson to stop using AnInterface's serializer here.
+    assertThat(request.getBody().readUtf8()).isEqualTo("{\"name\":\"value\"}");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
   }
 }
diff --git a/retrofit-converters/moshi/src/test/java/retrofit/MoshiConverterTest.java b/retrofit-converters/moshi/src/test/java/retrofit/MoshiConverterTest.java
index 072745cd9b..2e7213084a 100644
--- a/retrofit-converters/moshi/src/test/java/retrofit/MoshiConverterTest.java
+++ b/retrofit-converters/moshi/src/test/java/retrofit/MoshiConverterTest.java
@@ -20,29 +20,27 @@
 import com.squareup.moshi.JsonWriter;
 import com.squareup.moshi.Moshi;
 import com.squareup.moshi.ToJson;
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.ResponseBody;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
 import java.io.IOException;
-import okio.Buffer;
-import org.assertj.core.api.AbstractCharSequenceAssert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import retrofit.http.Body;
+import retrofit.http.POST;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
 
 public final class MoshiConverterTest {
-  private Converter converter;
-
-  interface Example {
+  interface AnInterface {
     String getName();
   }
 
-  static class Impl implements Example {
+  static class AnImplementation implements AnInterface {
     private final String theName;
 
-    Impl(String name) {
+    AnImplementation(String name) {
       theName = name;
     }
 
@@ -51,45 +49,75 @@ static class Impl implements Example {
     }
   }
 
-  static class ExampleAdapter {
-    @ToJson public void to(JsonWriter writer, Example example) throws IOException {
-      writer.beginObject();
-      writer.name("name").value(example.getName());
-      writer.endObject();
+  static class AnInterfaceAdapter {
+    @ToJson public void write(JsonWriter jsonWriter, AnInterface anInterface) throws IOException {
+      System.out.println("TO JSON: " + anInterface);
+      jsonWriter.beginObject();
+      jsonWriter.name("name").value(anInterface.getName());
+      jsonWriter.endObject();
     }
 
-    @FromJson public Example from(JsonReader reader) throws IOException {
-      throw new UnsupportedOperationException(); // Moshi requires this method to exist.
+    @FromJson public AnInterface read(JsonReader jsonReader) throws IOException {
+      jsonReader.beginObject();
+
+      String name = null;
+      while (jsonReader.hasNext()) {
+        switch (jsonReader.nextName()) {
+          case "name":
+            name = jsonReader.nextString();
+            break;
+        }
+      }
+
+      jsonReader.endObject();
+      return new AnImplementation(name);
     }
   }
 
+  interface Service {
+    @POST("/") Call<AnImplementation> anImplementation(@Body AnImplementation impl);
+    @POST("/") Call<AnInterface> anInterface(@Body AnInterface impl);
+  }
+
+  @Rule public final MockWebServerRule server = new MockWebServerRule();
+
+  private Service service;
+
   @Before public void setUp() {
-    Moshi gson = new Moshi.Builder()
-        .add(new ExampleAdapter())
+    Moshi moshi = new Moshi.Builder()
+        .add(new AnInterfaceAdapter())
+        .build();
+    Converter converter = new MoshiConverter(moshi);
+    Retrofit retrofit = new Retrofit.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(converter)
         .build();
-    converter = new MoshiConverter(gson);
+    service = retrofit.create(Service.class);
   }
 
-  @Test public void serialization() throws IOException {
-    RequestBody body = converter.toBody(new Impl("value"), Impl.class);
-    assertBody(body).isEqualTo("{\"theName\":\"value\"}");
-  }
+  @Test public void anInterface() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("{\"name\":\"value\"}"));
 
-  @Test public void serializationTypeUsed() throws IOException {
-    RequestBody body = converter.toBody(new Impl("value"), Example.class);
-    assertBody(body).isEqualTo("{\"name\":\"value\"}");
-  }
+    Call<AnInterface> call = service.anInterface(new AnImplementation("value"));
+    Response<AnInterface> response = call.execute();
+    AnInterface body = response.body();
+    assertThat(body.getName()).isEqualTo("value");
 
-  @Test public void deserialization() throws IOException {
-    ResponseBody body =
-        ResponseBody.create(MediaType.parse("text/plain"), "{\"theName\":\"value\"}");
-    Impl impl = (Impl) converter.fromBody(body, Impl.class);
-    assertEquals("value", impl.getName());
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readUtf8()).isEqualTo("{\"name\":\"value\"}");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
   }
 
-  private static AbstractCharSequenceAssert<?, String> assertBody(RequestBody body) throws IOException {
-    Buffer buffer = new Buffer();
-    body.writeTo(buffer);
-    return assertThat(buffer.readUtf8());
+  @Test public void anImplementation() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody("{\"theName\":\"value\"}"));
+
+    Call<AnImplementation> call = service.anImplementation(new AnImplementation("value"));
+    Response<AnImplementation> response = call.execute();
+    AnImplementation body = response.body();
+    assertThat(body.theName).isEqualTo("value");
+
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readUtf8()).isEqualTo("{\"theName\":\"value\"}");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/json; charset=UTF-8");
   }
 }
diff --git a/retrofit-converters/protobuf/src/test/java/retrofit/ProtoConverterTest.java b/retrofit-converters/protobuf/src/test/java/retrofit/ProtoConverterTest.java
index e10956d9bf..e04065a014 100644
--- a/retrofit-converters/protobuf/src/test/java/retrofit/ProtoConverterTest.java
+++ b/retrofit-converters/protobuf/src/test/java/retrofit/ProtoConverterTest.java
@@ -1,73 +1,119 @@
-// Copyright 2013 Square, Inc.
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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 retrofit;
 
 import com.google.protobuf.InvalidProtocolBufferException;
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.ResponseBody;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.List;
 import okio.Buffer;
 import okio.ByteString;
-import org.assertj.core.api.AbstractCharSequenceAssert;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import retrofit.http.Body;
+import retrofit.http.GET;
+import retrofit.http.POST;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static retrofit.PhoneProtos.Phone;
 
 public final class ProtoConverterTest {
-  private static final Phone PROTO = Phone.newBuilder().setNumber("(519) 867-5309").build();
-  private static final String ENCODED_PROTO = "Cg4oNTE5KSA4NjctNTMwOQ==";
+  interface Service {
+    @GET("/") Call<Phone> get();
+    @POST("/") Call<Phone> post(@Body Phone impl);
+    @GET("/") Call<String> wrongClass();
+    @GET("/") Call<List<String>> wrongType();
+  }
+
+  @Rule public final MockWebServerRule server = new MockWebServerRule();
 
-  private final ProtoConverter converter = new ProtoConverter();
+  private Service service;
 
-  @Test public void serialize() throws Exception {
-    RequestBody body = converter.toBody(PROTO, Phone.class);
-    assertThat(body.contentType().toString()).isEqualTo("application/x-protobuf");
-    assertBody(body).isEqualTo(ENCODED_PROTO);
+  @Before public void setUp() {
+    Retrofit retrofit = new Retrofit.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(new ProtoConverter())
+        .build();
+    service = retrofit.create(Service.class);
   }
 
-  @Test public void deserialize() throws Exception {
-    Object proto = converter.fromBody(protoResponse(ENCODED_PROTO), Phone.class);
-    assertThat(proto).isEqualTo(PROTO);
+  @Test public void serializeAndDeserialize() throws IOException, InterruptedException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<Phone> call = service.post(Phone.newBuilder().setNumber("(519) 867-5309").build());
+    Response<Phone> response = call.execute();
+    Phone body = response.body();
+    assertThat(body.getNumber()).isEqualTo("(519) 867-5309");
+
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readByteString()).isEqualTo(encoded);
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf");
+  }
+
+  @Test public void deserializeEmpty() throws IOException {
+    server.enqueue(new MockResponse());
+
+    Call<Phone> call = service.get();
+    Response<Phone> response = call.execute();
+    Phone body = response.body();
+    assertThat(body.hasNumber()).isFalse();
   }
 
-  @Test public void deserializeWrongClass() throws Exception {
+  @Test public void deserializeWrongClass() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<?> call = service.wrongClass();
     try {
-      converter.fromBody(protoResponse(ENCODED_PROTO), String.class);
+      call.execute();
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessage("Expected a protobuf message but was java.lang.String");
     }
   }
 
-  @Test public void deserializeWrongType() throws Exception {
+  @Test public void deserializeWrongType() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<?> call = service.wrongType();
     try {
-      converter.fromBody(protoResponse(ENCODED_PROTO), ArrayList.class.getGenericSuperclass());
+      call.execute();
       fail();
     } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Expected a raw Class<?> but was java.util.AbstractList<E>");
+      assertThat(e).hasMessage("Expected a raw Class<?> but was java.util.List<java.lang.String>");
     }
   }
 
-  @Test public void deserializeWrongValue() throws Exception {
+  @Test public void deserializeWrongValue() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("////");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<?> call = service.get();
     try {
-      converter.fromBody(protoResponse("////"), Phone.class);
+      call.execute();
       fail();
-    } catch (RuntimeException expected) {
-      assertThat(expected.getCause() instanceof InvalidProtocolBufferException);
+    } catch (RuntimeException e) {
+      assertThat(e.getCause()).isInstanceOf(InvalidProtocolBufferException.class)
+          .hasMessageContaining("input ended unexpectedly");
     }
   }
-
-  private static ResponseBody protoResponse(String encodedProto) {
-    return ResponseBody.create(MediaType.parse("application/x-protobuf"), ByteString.decodeBase64(
-        encodedProto).toByteArray());
-  }
-
-  private static AbstractCharSequenceAssert<?, String> assertBody(RequestBody body) throws IOException {
-    Buffer buffer = new Buffer();
-    body.writeTo(buffer);
-    return assertThat(buffer.readByteString().base64());
-  }
 }
diff --git a/retrofit-converters/simplexml/src/test/java/retrofit/MyObject.java b/retrofit-converters/simplexml/src/test/java/retrofit/MyObject.java
new file mode 100644
index 0000000000..398854c194
--- /dev/null
+++ b/retrofit-converters/simplexml/src/test/java/retrofit/MyObject.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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 retrofit;
+
+import org.simpleframework.xml.Default;
+import org.simpleframework.xml.DefaultType;
+import org.simpleframework.xml.Element;
+
+@Default(value = DefaultType.FIELD)
+final class MyObject {
+  @Element private String message;
+  @Element private int count;
+
+  public MyObject() {
+  }
+
+  public MyObject(String message, int count) {
+    this.message = message;
+    this.count = count;
+  }
+
+  public void setMessage(String message) {
+    this.message = message;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public void setCount(int count) {
+    this.count = count;
+  }
+
+  public int getCount() {
+    return count;
+  }
+
+  @Override public int hashCode() {
+    int result = 1;
+    result = result * 31 + count;
+    result = result * 31 + (message == null ? 0 : message.hashCode());
+    return result;
+  }
+
+  @Override public boolean equals(Object obj) {
+    if (obj == this) return true;
+    if (!(obj instanceof MyObject)) return false;
+    MyObject other = (MyObject) obj;
+    return count == other.count
+        && (message == null ? other.message == null : message.equals(other.message));
+  }
+}
diff --git a/retrofit-converters/simplexml/src/test/java/retrofit/SimpleXmlConverterTest.java b/retrofit-converters/simplexml/src/test/java/retrofit/SimpleXmlConverterTest.java
index e47c44a6fb..1c1d0f9a74 100644
--- a/retrofit-converters/simplexml/src/test/java/retrofit/SimpleXmlConverterTest.java
+++ b/retrofit-converters/simplexml/src/test/java/retrofit/SimpleXmlConverterTest.java
@@ -1,128 +1,100 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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 retrofit;
 
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.ResponseBody;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
 import java.io.IOException;
-import okio.Buffer;
-import org.assertj.core.api.AbstractCharSequenceAssert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.simpleframework.xml.Default;
-import org.simpleframework.xml.DefaultType;
-import org.simpleframework.xml.Element;
+import org.simpleframework.xml.core.ElementException;
 import org.simpleframework.xml.core.Persister;
 import org.simpleframework.xml.stream.Format;
 import org.simpleframework.xml.stream.HyphenStyle;
 import org.simpleframework.xml.stream.Verbosity;
+import retrofit.http.Body;
+import retrofit.http.GET;
+import retrofit.http.POST;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
 
 public class SimpleXmlConverterTest {
-  private static final MediaType MEDIA_TYPE = MediaType.parse("application/xml; charset=UTF-8");
-  private static final MyObject OBJ = new MyObject("hello world", 10);
-  private static final String XML =
-      "<my-object><message>hello world</message><count>10</count></my-object>";
+  interface Service {
+    @GET("/") Call<MyObject> get();
+    @POST("/") Call<MyObject> post(@Body MyObject impl);
+    @GET("/") Call<String> wrongClass();
+  }
+
+  @Rule public final MockWebServerRule server = new MockWebServerRule();
 
-  private Converter converter;
+  private Service service;
 
   @Before public void setUp() {
     Format format = new Format(0, null, new HyphenStyle(), Verbosity.HIGH);
     Persister persister = new Persister(format);
-    converter = new SimpleXmlConverter(persister);
-  }
-
-  @Test public void serialize() throws Exception {
-    RequestBody body = converter.toBody(OBJ, MyObject.class);
-    assertThat(body.contentType()).isEqualTo(MEDIA_TYPE);
-    assertBody(body).isEqualTo(XML);
-  }
-
-  @Test public void deserialize() throws Exception {
-    ResponseBody body = ResponseBody.create(MEDIA_TYPE, XML);
-    MyObject result = (MyObject) converter.fromBody(body, MyObject.class);
-    assertThat(result).isEqualTo(OBJ);
+    Converter converter = new SimpleXmlConverter(persister);
+    Retrofit retrofit = new Retrofit.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(converter)
+        .build();
+    service = retrofit.create(Service.class);
   }
 
-  @Test public void deserializeWrongValue() throws Exception {
-    ResponseBody body = ResponseBody.create(MEDIA_TYPE, "<myObject><foo/><bar/></myObject>");
-    try {
-      converter.fromBody(body, MyObject.class);
-    } catch (RuntimeException ignored) {
-    }
-  }
+  @Test public void bodyWays() throws IOException, InterruptedException {
+    server.enqueue(new MockResponse().setBody(
+        "<my-object><message>hello world</message><count>10</count></my-object>"));
 
-  @Test public void deserializeWrongClass() throws Exception {
-    ResponseBody body = ResponseBody.create(MEDIA_TYPE, XML);
-    Object result = converter.fromBody(body, String.class);
-    assertThat(result).isNull();
-  }
+    Call<MyObject> call = service.post(new MyObject("hello world", 10));
+    Response<MyObject> response = call.execute();
+    MyObject body = response.body();
+    assertThat(body.getMessage()).isEqualTo("hello world");
+    assertThat(body.getCount()).isEqualTo(10);
 
-  private static AbstractCharSequenceAssert<?, String> assertBody(RequestBody body)
-      throws IOException {
-    Buffer buffer = new Buffer();
-    body.writeTo(buffer);
-    return assertThat(buffer.readUtf8());
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readUtf8()).isEqualTo(
+        "<my-object><message>hello world</message><count>10</count></my-object>");
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/xml; charset=UTF-8");
   }
 
-  @Default(value = DefaultType.FIELD) static class MyObject {
-    @Element private String message;
-    @Element private int count;
-
-    public MyObject() {
-    }
-
-    public MyObject(String message, int count) {
-      this.message = message;
-      this.count = count;
-    }
-
-    public void setMessage(String message) {
-      this.message = message;
-    }
-
-    public String getMessage() {
-      return message;
-    }
-
-    public void setCount(int count) {
-      this.count = count;
-    }
+  @Test public void deserializeWrongValue() throws IOException {
+    server.enqueue(new MockResponse().setBody("<myObject><foo/><bar/></myObject>"));
 
-    public int getCount() {
-      return count;
+    Call<?> call = service.get();
+    try {
+      call.execute();
+      fail();
+    } catch (RuntimeException e) {
+      assertThat(e.getCause()).isInstanceOf(ElementException.class)
+          .hasMessageStartingWith("Element 'foo' does not have a match in class retrofit.MyObject");
     }
+  }
 
-    @Override public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + count;
-      result = prime * result + ((message == null) ? 0 : message.hashCode());
-      return result;
-    }
+  @Test public void deserializeWrongClass() throws IOException {
+    server.enqueue(new MockResponse().setBody(
+        "<my-object><message>hello world</message><count>10</count></my-object>"));
 
-    @Override public boolean equals(Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null) {
-        return false;
-      }
-      if (getClass() != obj.getClass()) {
-        return false;
-      }
-      MyObject other = (MyObject) obj;
-      if (count != other.count) {
-        return false;
-      }
-      if (message == null) {
-        if (other.message != null) {
-          return false;
-        }
-      } else if (!message.equals(other.message)) {
-        return false;
-      }
-      return true;
+    Call<?> call = service.wrongClass();
+    try {
+      call.execute();
+      fail();
+    } catch (RuntimeException e) {
+      assertThat(e).hasMessage("Could not deserialize body as class java.lang.String");
     }
   }
 }
diff --git a/retrofit-converters/wire/src/test/java/retrofit/Person.java b/retrofit-converters/wire/src/test/java/retrofit/Person.java
deleted file mode 100644
index daf0bc3222..0000000000
--- a/retrofit-converters/wire/src/test/java/retrofit/Person.java
+++ /dev/null
@@ -1,248 +0,0 @@
-// Code generated by Wire protocol buffer compiler, do not edit.
-// Source file: ../wire-runtime/src/test/proto/person.proto
-package retrofit;
-
-import com.squareup.wire.Message;
-import com.squareup.wire.ProtoEnum;
-import com.squareup.wire.ProtoField;
-import java.util.Collections;
-import java.util.List;
-
-import static com.squareup.wire.Message.Datatype.ENUM;
-import static com.squareup.wire.Message.Datatype.INT32;
-import static com.squareup.wire.Message.Datatype.STRING;
-import static com.squareup.wire.Message.Label.REPEATED;
-import static com.squareup.wire.Message.Label.REQUIRED;
-
-public final class Person extends Message {
-
-  public static final String DEFAULT_NAME = "";
-  public static final Integer DEFAULT_ID = 0;
-  public static final String DEFAULT_EMAIL = "";
-  public static final List<PhoneNumber> DEFAULT_PHONE = Collections.emptyList();
-
-  /**
-   * The customer's full name.
-   */
-  @ProtoField(tag = 1, type = STRING, label = REQUIRED)
-  public final String name;
-
-  /**
-   * The customer's ID number.
-   */
-  @ProtoField(tag = 2, type = INT32, label = REQUIRED)
-  public final Integer id;
-
-  /**
-   * Email address for the customer.
-   */
-  @ProtoField(tag = 3, type = STRING)
-  public final String email;
-
-  /**
-   * A list of the customer's phone numbers.
-   */
-  @ProtoField(tag = 4, label = REPEATED)
-  public final List<PhoneNumber> phone;
-
-  public Person(String name, Integer id, String email, List<PhoneNumber> phone) {
-    this.name = name;
-    this.id = id;
-    this.email = email;
-    this.phone = immutableCopyOf(phone);
-  }
-
-  private Person(Builder builder) {
-    this(builder.name, builder.id, builder.email, builder.phone);
-    setBuilder(builder);
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (other == this) return true;
-    if (!(other instanceof Person)) return false;
-    Person o = (Person) other;
-    return equals(name, o.name)
-        && equals(id, o.id)
-        && equals(email, o.email)
-        && equals(phone, o.phone);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = hashCode;
-    if (result == 0) {
-      result = name != null ? name.hashCode() : 0;
-      result = result * 37 + (id != null ? id.hashCode() : 0);
-      result = result * 37 + (email != null ? email.hashCode() : 0);
-      result = result * 37 + (phone != null ? phone.hashCode() : 1);
-      hashCode = result;
-    }
-    return result;
-  }
-
-  public static final class Builder extends Message.Builder<Person> {
-
-    public String name;
-    public Integer id;
-    public String email;
-    public List<PhoneNumber> phone;
-
-    public Builder() {
-    }
-
-    public Builder(Person message) {
-      super(message);
-      if (message == null) return;
-      this.name = message.name;
-      this.id = message.id;
-      this.email = message.email;
-      this.phone = copyOf(message.phone);
-    }
-
-    /**
-     * The customer's full name.
-     */
-    public Builder name(String name) {
-      this.name = name;
-      return this;
-    }
-
-    /**
-     * The customer's ID number.
-     */
-    public Builder id(Integer id) {
-      this.id = id;
-      return this;
-    }
-
-    /**
-     * Email address for the customer.
-     */
-    public Builder email(String email) {
-      this.email = email;
-      return this;
-    }
-
-    /**
-     * A list of the customer's phone numbers.
-     */
-    public Builder phone(List<PhoneNumber> phone) {
-      this.phone = checkForNulls(phone);
-      return this;
-    }
-
-    @Override
-    public Person build() {
-      checkRequiredFields();
-      return new Person(this);
-    }
-  }
-
-  public enum PhoneType
-      implements ProtoEnum {
-    MOBILE(0),
-    HOME(1),
-    /**
-     * Could be phone or fax.
-     */
-    WORK(2);
-
-    private final int value;
-
-    private PhoneType(int value) {
-      this.value = value;
-    }
-
-    @Override
-    public int getValue() {
-      return value;
-    }
-  }
-
-  public static final class PhoneNumber extends Message {
-
-    public static final String DEFAULT_NUMBER = "";
-    public static final PhoneType DEFAULT_TYPE = PhoneType.HOME;
-
-    /**
-     * The customer's phone number.
-     */
-    @ProtoField(tag = 1, type = STRING, label = REQUIRED)
-    public final String number;
-
-    /**
-     * The type of phone stored here.
-     */
-    @ProtoField(tag = 2, type = ENUM)
-    public final PhoneType type;
-
-    public PhoneNumber(String number, PhoneType type) {
-      this.number = number;
-      this.type = type;
-    }
-
-    private PhoneNumber(Builder builder) {
-      this(builder.number, builder.type);
-      setBuilder(builder);
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (other == this) return true;
-      if (!(other instanceof PhoneNumber)) return false;
-      PhoneNumber o = (PhoneNumber) other;
-      return equals(number, o.number)
-          && equals(type, o.type);
-    }
-
-    @Override
-    public int hashCode() {
-      int result = hashCode;
-      if (result == 0) {
-        result = number != null ? number.hashCode() : 0;
-        result = result * 37 + (type != null ? type.hashCode() : 0);
-        hashCode = result;
-      }
-      return result;
-    }
-
-    public static final class Builder extends Message.Builder<PhoneNumber> {
-
-      public String number;
-      public PhoneType type;
-
-      public Builder() {
-      }
-
-      public Builder(PhoneNumber message) {
-        super(message);
-        if (message == null) return;
-        this.number = message.number;
-        this.type = message.type;
-      }
-
-      /**
-       * The customer's phone number.
-       */
-      public Builder number(String number) {
-        this.number = number;
-        return this;
-      }
-
-      /**
-       * The type of phone stored here.
-       */
-      public Builder type(PhoneType type) {
-        this.type = type;
-        return this;
-      }
-
-      @Override
-      public PhoneNumber build() {
-        checkRequiredFields();
-        return new PhoneNumber(this);
-      }
-    }
-  }
-}
diff --git a/retrofit-converters/wire/src/test/java/retrofit/Phone.java b/retrofit-converters/wire/src/test/java/retrofit/Phone.java
new file mode 100644
index 0000000000..e8a47989d8
--- /dev/null
+++ b/retrofit-converters/wire/src/test/java/retrofit/Phone.java
@@ -0,0 +1,69 @@
+// Code generated by Wire protocol buffer compiler, do not edit.
+// Source file: ../wire-runtime/src/test/proto/person.proto
+package retrofit;
+
+import com.squareup.wire.Message;
+import com.squareup.wire.ProtoField;
+
+import static com.squareup.wire.Message.Datatype.STRING;
+import static com.squareup.wire.Message.Label.OPTIONAL;
+
+public final class Phone extends Message {
+
+  public static final String DEFAULT_PHONE = "";
+
+  @ProtoField(tag = 1, type = STRING, label = OPTIONAL)
+  public final String number;
+
+  public Phone(String number) {
+    this.number = number;
+  }
+
+  private Phone(Builder builder) {
+    this(builder.number);
+    setBuilder(builder);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == this) return true;
+    if (!(other instanceof Phone)) return false;
+    Phone o = (Phone) other;
+    return equals(number, o.number);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = hashCode;
+    if (result == 0) {
+      result = number != null ? number.hashCode() : 0;
+      hashCode = result;
+    }
+    return result;
+  }
+
+  public static final class Builder extends Message.Builder<Phone> {
+
+    public String number;
+
+    public Builder() {
+    }
+
+    public Builder(Phone message) {
+      super(message);
+      if (message == null) return;
+      this.number = message.number;
+    }
+
+    public Builder number(String name) {
+      this.number = name;
+      return this;
+    }
+
+    @Override
+    public Phone build() {
+      checkRequiredFields();
+      return new Phone(this);
+    }
+  }
+}
diff --git a/retrofit-converters/wire/src/test/java/retrofit/WireConverterTest.java b/retrofit-converters/wire/src/test/java/retrofit/WireConverterTest.java
index b72c1f5fe0..47e615f70b 100644
--- a/retrofit-converters/wire/src/test/java/retrofit/WireConverterTest.java
+++ b/retrofit-converters/wire/src/test/java/retrofit/WireConverterTest.java
@@ -1,72 +1,116 @@
-// Copyright 2013 Square, Inc.
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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 retrofit;
 
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.RequestBody;
-import com.squareup.okhttp.ResponseBody;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule;
+import java.io.EOFException;
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.List;
 import okio.Buffer;
 import okio.ByteString;
-import org.assertj.core.api.AbstractCharSequenceAssert;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import retrofit.http.Body;
+import retrofit.http.GET;
+import retrofit.http.POST;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 
 public final class WireConverterTest {
-  private static final Person PROTO =
-      new Person.Builder().id(42).name("Omar Little").email("omar@theking.org").build();
-  private static final String ENCODED_PROTO = "CgtPbWFyIExpdHRsZRAqGhBvbWFyQHRoZWtpbmcub3Jn";
+  interface Service {
+    @GET("/") Call<Phone> get();
+    @POST("/") Call<Phone> post(@Body Phone impl);
+    @GET("/") Call<String> wrongClass();
+    @GET("/") Call<List<String>> wrongType();
+  }
+
+  @Rule public final MockWebServerRule server = new MockWebServerRule();
 
-  private final WireConverter converter = new WireConverter();
+  private Service service;
 
-  @Test public void serialize() throws Exception {
-    RequestBody body = converter.toBody(PROTO, Person.class);
-    assertThat(body.contentType().toString()).isEqualTo("application/x-protobuf");
-    assertBody(body).isEqualTo(ENCODED_PROTO);
+  @Before public void setUp() {
+    Retrofit retrofit = new Retrofit.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(new WireConverter())
+        .build();
+    service = retrofit.create(Service.class);
   }
 
-  @Test public void deserialize() throws Exception {
-    Object proto = converter.fromBody(protoResponse(ENCODED_PROTO), Person.class);
-    assertThat(proto).isEqualTo(PROTO);
+  @Test public void serializeAndDeserialize() throws IOException, InterruptedException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<Phone> call = service.post(new Phone("(519) 867-5309"));
+    Response<Phone> response = call.execute();
+    Phone body = response.body();
+    assertThat(body.number).isEqualTo("(519) 867-5309");
+
+    RecordedRequest request = server.takeRequest();
+    assertThat(request.getBody().readByteString()).isEqualTo(encoded);
+    assertThat(request.getHeader("Content-Type")).isEqualTo("application/x-protobuf");
+  }
+
+  @Test public void deserializeEmpty() throws IOException {
+    server.enqueue(new MockResponse());
+
+    Call<Phone> call = service.get();
+    Response<Phone> response = call.execute();
+    Phone body = response.body();
+    assertThat(body.number).isNull();
   }
 
-  @Test public void deserializeWrongClass() throws Exception {
+  @Test public void deserializeWrongClass() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<?> call = service.wrongClass();
     try {
-      converter.fromBody(protoResponse(ENCODED_PROTO), String.class);
+      call.execute();
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessage("Expected a proto message but was java.lang.String");
     }
   }
 
-  @Test public void deserializeWrongType() throws Exception {
+  @Test public void deserializeWrongType() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("Cg4oNTE5KSA4NjctNTMwOQ==");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<?> call = service.wrongType();
     try {
-      converter.fromBody(protoResponse(ENCODED_PROTO),
-          ArrayList.class.getGenericSuperclass());
+      call.execute();
       fail();
     } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Expected a raw Class<?> but was java.util.AbstractList<E>");
+      assertThat(e).hasMessage("Expected a raw Class<?> but was java.util.List<java.lang.String>");
     }
   }
 
-  @Test public void deserializeWrongValue() throws Exception {
+  @Test public void deserializeWrongValue() throws IOException {
+    ByteString encoded = ByteString.decodeBase64("////");
+    server.enqueue(new MockResponse().setBody(new Buffer().write(encoded)));
+
+    Call<?> call = service.get();
     try {
-      converter.fromBody(protoResponse("////"), Person.class);
+      call.execute();
       fail();
-    } catch (IOException ignored) {
+    } catch (EOFException ignored) {
     }
   }
-
-  private static ResponseBody protoResponse(String encodedProto) {
-    return ResponseBody.create(MediaType.parse("application/x-protobuf"),
-        ByteString.decodeBase64(encodedProto).toByteArray());
-  }
-
-  private static AbstractCharSequenceAssert<?, String> assertBody(RequestBody body) throws IOException {
-    Buffer buffer = new Buffer();
-    body.writeTo(buffer);
-    return assertThat(buffer.readByteString().base64());
-  }
 }