4

I would like to mock an object passed into Gson via TypeAdapter like this:

@RunWith(MockitoJUnitRunner.class)
public class SceneExporterTest {

@Test
public void testWriter() {
    List<SceneObject> sceneObjects = mockSceneObjects();
    Gson gson = new GsonBuilder().registerTypeAdapter(SceneObject.class, new SceneExporter()).create();

    String s = gson.toJson(sceneObjects); //This method ends up with an exception.
}

private List<SceneObject> mockSceneObjects() {
    List<SceneObject> sceneObjects = new LinkedList<>();
    for (int i = 0; i < 50; i++) {
        sceneObjects.add(mockSceneObject(i));
    }
    return sceneObjects;
}

private SceneObject mockSceneObject(int i) {
    SceneObject sceneObject = mock(SceneObject.class);
    //...
    return sceneObject;
}

}

My type adapter:

public class SceneExporter extends TypeAdapter<SceneObject> {

    @Override
    public void write(JsonWriter out, SceneObject value) throws IOException {
        out.name("position");
        out.value(toValue(value.getPosition()));
        out.name("scale");
        out.value(toValue(value.getScale()));
        out.name("rotation");
        out.value(toValue(value.getRotation()));
    }

    @Override
    public SceneObject read(JsonReader in) throws IOException {
        return null;
    }
}

But I end up which such exception:

java.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: com.editor.api.scene.objects.SceneObject. Forgot to register a type adapter?

Scene object is pretty heavy object and I don't want to instantinate it normally within test. So is there a possibility to just mock it? I also would like not to use Spies.

Michał Stochmal
  • 5,895
  • 4
  • 36
  • 44

1 Answers1

4

The runtime type of an instance of SceneObject created in this way: mock(SceneObject.class) is SceneObject$MockitoMock$<SOMEID>.

The runtime type of an instance of SceneObject created in this way: new SceneObject() is SceneObject.

So, when Gson's TypeAdapterRuntimeTypeWrapper looks for a registered TypeAdapter in its context for a mocked object it will not find one because your SceneExporter has been registered against SceneObject.class.

What this is really saying is that a mocked SceneObject is not of type SceneObject (it is actually a sub class of SceneObject) hence Gson will not find your bespoke type adapter.

If you declare Gson like this ...

Gson gson = new GsonBuilder().registerTypeAdapter(mock(SceneObject.class).getClass(), new SceneExporter()).create();

... then it will find your bespoke type adapter but that registration looks 'off' doesn't it? It feels like a test-only specialisation.

If you really must mock SceneObject then I think you'll need to register an inheritance aware type adapter but if you are doing that just to support this test case then that feels like a test-only limitation bleeding into the 'main' source tree. So, perhaps the simplest solutions are:

  • Create real instances of SceneObject
  • Use a Mockito Spy to stub out the specific SceneObject getters which are invoked in SceneExporter
glytching
  • 44,936
  • 9
  • 114
  • 120