google / gwtmockito

Better GWT unit testing
https://google.github.io/gwtmockito
Apache License 2.0
157 stars 50 forks source link

Mocked UiBinder type is erased #51

Open cushon opened 9 years ago

cushon commented 9 years ago

I think is is distinct from #50.

In the following example the type of the UiBinder is erased when it is mocked, causing a ClassCastException.

I think there's type information available at runtime that could be used to fixed this. For example, see mockito's handling of RETURNS_DEEP_STUBS with generic types:

Here's the example:

@RunWith(GwtMockitoTestRunner.class)
public class MyWidgetTest {

  interface Appearance {
    // Mocked type will be erased to `UIBinder`
    UiBinder<? extends Widget, ?> uiBinder();
  }

  static class Widget {
    public Widget() {
      Appearance appearance = GWT.create(Appearance.class);
      // Assignment fails with CCE
      Widget widget = appearance.uiBinder().createAndBindUi(null);
    }
  }

  @Before
  public void setUp() {
    new Widget();
  }

  @Test
  public void simpleTest() {
    System.err.println("Hello world");
  }
}

Full repro:

mkdir -p src/test/java/mockitobug/
curl https://gist.githubusercontent.com/cushon/8c231f89708dadcf0075/raw/d9e50510499132037cac47dbd8ad67854fe8b429/MyWidgetTest.java > src/test/java/mockitobug/MyWidgetTest.java
curl https://gist.githubusercontent.com/cushon/50a9cbe451e7d6607a65/raw/94d77af02627d2b7b1756ff14e7a396514d7aeaa/pom.xml > pom.xml
mvn test
Running mockitobug.MyWidgetTest
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.514 sec <<< FAILURE!
simpleTest(mockitobug.MyWidgetTest)  Time elapsed: 0.216 sec  <<< ERROR!
java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$99e9e895 cannot be cast to mockitobug.MyWidgetTest$Widget
    at mockitobug.MyWidgetTest$Widget.<init>(MyWidgetTest.java:22)
    at mockitobug.MyWidgetTest.setUp(MyWidgetTest.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        ...
ekuefler commented 9 years ago

Thanks for the detailed report. I agree we should have the type information available somewhere at runtime, but it looks quite tricky to get it. I'll take a look at this when I get a chance (which might be a while), or feel free to give it a try yourself.

To some extent this is an issue with the underlying Mockito implementation. We construct mocks using Mockito's ReturnsMocks setting, so presumably it would display the same behavior if used directly. It would be nice if there were a solution that worked by changing how we used mockito rather than adding additional logic on top of that. One thing to explore would be making ReturnsCustomMocks extend ReturnsDeepStubs rather than ReturnsMocks. I experimented with this briefly and it solved the issue you reported, but seemed to create others.