Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$$EnhancerByMockitoWithCGLIB$$29edae7f cannot be cast to com.google.gwt.user.client.Element" while trying to test a widget extending SimpleLayoutPanel #4

Open
konradstrack opened this issue May 29, 2013 · 27 comments

Comments

@konradstrack
Copy link

Let's take the following example:

public class MyPanel extends SimpleLayoutPanel {

    private Label label;

    public MyPanel() {
        label = GWT.create(Label.class);
        label.setText("It's my panel");

        add(label);
    }

}

And a test case:

@RunWith(GwtMockitoTestRunner.class)
public class MyPanelTests {

    private MyPanel myPanel;

    @Before
    public void setUp() {
        myPanel = new MyPanel();
    }

    @Test
    public void testTruth() {
        assertTrue(true);
    }
}

Running this test case will result in the following exception:

java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$$EnhancerByMockitoWithCGLIB$$bd5b9f31 cannot be cast to com.google.gwt.user.client.Element
    at com.google.gwt.user.client.DOM.createDiv(DOM.java:149)
    at com.google.gwt.user.client.ui.SimplePanel.<init>(SimplePanel.java:35)
    at com.google.gwt.user.client.ui.SimpleLayoutPanel.<init>(SimpleLayoutPanel.java:31)
    at cern.ais.plugin.gwt.client.layout.view.MyPanel.<init>(MyPanel.java:11)
    at cern.ais.plugin.gwt.client.layout.view.MyPanelTests.setUp(MyPanelTests.java:17)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at com.google.gwtmockito.GwtMockitoTestRunner.run(GwtMockitoTestRunner.java:102)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:24)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

If MyPanel extends Composite, everything works fine, but with SimpleLayoutPanel (or just SimplePanel) the result is the same as the one above.

Is this a known limitation?

In many cases widgets can be refactored to extend Composite, but is there a way of testing with gwtmockito those that have to extend something else than Composite, or is using GWTTestCase required in such cases?

@ekuefler
Copy link
Collaborator

Hm, it definitely shouldn't matter what your widget is extending. I'll dive into this tomorrow to try and figure out why it's breaking. Thanks for the detailed report!

@ekuefler
Copy link
Collaborator

So the underlying problem here is pretty interesting (skip to the bottom if you just want to see the fix). The issue is with the methods from DOM.java in GWT that create elements. LayoutPanel uses these directly instead of going through the nicer Document API. They look like this:

public static Element createDiv() {
  return Document.get().createDivElement().cast();
}

The issue is the cast() method, which is designed to coerce any JavaScriptObject into any other. This is fine when GWT is compiled to Javascript, but it causes some havoc in Java when you try to cast one class to something it doesn't extend. That's what's happening in GWT here - Element in this case is actually com.google.gwt.user.client.Element, which is a sibling of com.google.gwt.dom.client.DivElement, not a parent. So the cast fails.

It would be nice if we could just stub calls to cast() to return new mocks of the appropriate type (so it's not really a cast at all), but unfortunately it's impossible to figure out what type we actually want to cast to at runtime since that information is only contained in the generic definition of cast, which is lost at runtime. Too bad cast doesn't take the target class as an argument. So implementing cast() in a completely satisfying way probably isn't going to happen.

What I can do is stub out the create methods in DOM entirely to return mock Elements. This should avoid breakages when using code that calls into DOM as LayoutPanel does. The only case that this won't cover is when code does a weird cast like IFrameElement e = Document.get().createDivElement().cast(). But it's pretty unclear how GwtMockito should handle this case anyways and it seems fine to ask people to stick with GWTTestCase if they want to be that weird.

So, long story short, I've pushed a workaround for this in commit ce7323e. Want to try building GwtMockito from HEAD and seeing if that solves your problem?

@konradstrack
Copy link
Author

Thank you for the very detailed answer. Unfortunately, the problem has been solved only partially.

First of all, I can successfully build commit ce7323e but the latest one (8f7f4af) results in three errors during Maven test phase. You can find the Surefire output here: https://gist.github.com/konradstrack/3a457db40d38a154b976#file-gistfile1-txt

But when I use ce7323e, the test passes if I don't attach any child widgets to MyPanel. So, this is alright:

public class MyPanel extends SimpleLayoutPanel {

    public MyPanel() {
        Label label = GWT.create(Label.class);
        label.setText("It's my panel");

//        add(label);
    }
}

But the original test results in an AssertionError. So, if I try to add a Label:

public class MyPanel extends SimpleLayoutPanel {

    public MyPanel() {
        Label label = GWT.create(Label.class);
        label.setText("It's my panel");

        add(label);
    }
}

I get:

java.lang.AssertionError
    at com.google.gwt.user.client.ui.Panel.adopt(Panel.java:126)
    at com.google.gwt.user.client.ui.SimpleLayoutPanel.setWidget(SimpleLayoutPanel.java:89)
    at com.google.gwt.user.client.ui.SimplePanel.add(SimplePanel.java:69)
    at cern.ais.plugin.gwt.client.layout.view.MyPanel.<init>(MyPanel.java:13)
    at cern.ais.plugin.gwt.client.layout.view.MyPanelTests.setUp(MyPanelTests.java:17)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at com.google.gwtmockito.GwtMockitoTestRunner.run(GwtMockitoTestRunner.java:104)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:77)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

@ekuefler
Copy link
Collaborator

ekuefler commented Jun 1, 2013

Oops, last-minute visibility change broke the tests. Pushed a fix for that.

The next problem is interesting. The reason GwtMockito works well with Composites is that all the code for widgets lives behind fields that can be mocked out. This gets harder when you're extending a real widget. For your case, it would work fine if you extended Composite and wrapped a LayoutPanel since we could mock out the add() call, but since you're extending LayoutPanel instead it gets harder to mock since your class inherits a real add method.

But extending non-Composite widgets is common enough that GwtMockito should find a way to make it work. I think what it can do is stub out the implementations for all of the methods in a bunch of common base widget classes - UIObject, Widget, Composite, and all the Panels seem like good candidates. This effectively allows us to "mock" the superclass of the widget. It's still probably not as testable as using a Composite since you can't stub and verify the methods individually, but it should at least allow subclasses to be instantiated and their methods be called without breaking.

I've implemented this in 11cb40a. Let me know if that helps with your use case.

@konradstrack
Copy link
Author

I have just tested GwtMockito from HEAD on the original test case and it seems to work perfectly fine. Thank you for fixing the problem, and in such short time.

There is however one other possible use case that I don't really know how to handle, and if it is in any way supported by GwtMockito right now. Everything seems to be fine if you extend something that comes from GWT. But if, for instance, my widget would be extending something like a SimpleContainer from GXT it would fall into similar problems.

Would it be enough to create an additional provider for SimpleContainer, or is there another mechanism that would allow to provide mocks for parents which are not standard GWT widgets?

PS. Sorry for prematurely closing the issue.

@ekuefler
Copy link
Collaborator

ekuefler commented Jun 9, 2013

It probably doesn't make sense to add SimpleContainer to the list of things GwtMockito knows about, since that would introduce a dependency from GwtMockito to GXT.

However, it probably would be a good idea to make the list user-configurable. I'm not aware of an easy way to pass arguments to test runners, but we could probably accomplish this by making GwtMockitoTestRunner overridable, such that the set of classes to stub could be configured via a protected method. So if you're using GXT, you could define an override of GwtMockitoTestRunner that adds all the GXT base classes to the stub list, and use that in your tests, How does that sound?

@konradstrack
Copy link
Author

I totally agree that it doesn't make sense to add any weird dependencies to GwtMockito - GXT is not the only set of widgets out there, and SimpleContainer is probably not the only thing that may need mocking.

The proposed solution seems very good, and pretty convenient too - for sure much more convenient than stubbing classes in every test case.

@ekuefler
Copy link
Collaborator

Okay, I've provided a protected method that you can override in 2e9f9f4. Check it out and let me know if it works like you want it to.

@ghost
Copy link

ghost commented Jun 17, 2013

Hi! I've took over that issue from @konradstrack. Finally it works! The only thing is that the default class list is not full at all. For example the ListBox expose this method.

  private SelectElement getSelectElement() {
    return getElement().cast();
  }

And it's failing for the mentioned reason.
My proposal is to find all classes programatically in GWT ( and expose some method to find all classes to mock in 3rd party libs like GXT) which calling the cast() function and automatically include them into list. Should not be that hard thing.

@ekuefler
Copy link
Collaborator

Yeah, the default class list is intentionally pretty narrow and I'm not including every GWT widget in it. I want to start by being fairly conservative about what goes on the list such that there's as little magic happening as possible. I think I'll wait a while to see what people think of the current setup and then re-examine if there's demand for expanding the default list.

@justinmk
Copy link
Contributor

This worked well for me. For Sencha GXT, I am using the following:

@Override
protected Collection<Class<?>> getClassesToStub() {
    Collection<Class<?>> classes = super.getClassesToStub();
    classes.add(XElement.class);
    classes.add(SimpleContainer.class);
    classes.add(ResizeContainer.class);
    classes.add(Container.class);
    classes.add(StyleInjectorHelper.class);
    classes.add(NorthSouthContainer.class);
    classes.add(Draggable.class);
    classes.add(GridDragSource.class);
    return classes;
}

Also, Sencha GXT actually calls getElement().cast() in the constructor of some GXT widgets, such as Draggable. To deal with that, I stub the getElement() method of the injected widget(s):

    XElement el = XElement.createElement("div");
    Mockito.when(mockWidget.getElement()).thenReturn(el);

@nikp
Copy link

nikp commented Sep 11, 2013

I'm having this identical issue, and upgrading to GWTMockito 1.1.1 did not help

In my case I'm using a custom DataTable that extends com.google.gwt.user.cellview.client.CellTable

What fails is line 613 in CellTable

table = getElement().cast();

java.lang.ClassCastException: com.google.gwt.user.client.Element$$EnhancerByMockitoWithCGLIB$$55b005a0 cannot be cast to com.google.gwt.dom.client.TableElement
at com.google.gwt.user.cellview.client.CellTable.(CellTable.java:613)

The problem is getElement return type is com.google.gwt.user.client.Element, which is a sibling of com.google.gwt.dom.client.TableElement. Their mutual base class is com.google.gwt.dom.client.Element

How would I go about fixing this with explicit mocking in my test? I tried the following, but it doesn't compile due to the sibling issue:

when(myTable.getElement()).thenReturn(mock(TableElement.class));

But honestly I'm not even sure how this could work, because since this happens in the constructor of CellTable, there is a catch 22. I can't mock the object until I construct it and I can't construct it without getElement be mocked. How would one mock out a base class?

@nikp
Copy link

nikp commented Sep 16, 2013

There is a further issue with the suggested workaround in #4 (comment)

As suggested, the following snippet fixes the problem for Labels created as part of code executed within the test:

        DivElement divElement = mock(DivElement.class);
        when(divElement.getTagName()).thenReturn("div");
        final Document document = Document.get();
        when(document.createDivElement()).thenReturn(divElement);

However, if the Label is part of a class or base class for a type that the test is attempting to @GwtMock, the failing assertion code will execute before the mocks can get setup and executed

@ekuefler
Copy link
Collaborator

@nikp - if your problem is a misbehaving base class in CellTable, you might be able to fix it by adding CellTable to the list of base classes to stub. Try defining a subclass of GwtMockitoTestRunner that overrides a method like this:

  @Override
  protected Collection<Class<?>> getClassesToStub() {
    Collection<Class<?>> classes = super.getClassesToStub();
    classes.add(CellTable.class);
    return classes;
  }

Can you let me know if that helps? If so I could add CellTable to the list of classes to stub by default.

@nikp
Copy link

nikp commented Sep 19, 2013

Thanks @ekuefler, I will try that!

@avetokhin
Copy link

Hi. There is a problem with com.google.gwt.user.client.ui.Image class too. Image uses private inner class com.google.gwt.user.client.ui.Image.UnclippedState and I don't have access to it. When I create new Image("anyUrl") (in my view class which I want to test) I have an exception: com.google.gwt.user.client.Element$$EnhancerByMockitoWithCGLIB$$5830e7d1 cannot be cast to com.google.gwt.dom.client.ImageElement from UnclippedState's method:

@OverRide
public ImageElement getImageElement(Image image) {
return image.getElement().cast();
}

Because Image is mocked it returns Element which cannot be cast to ImageElement.

Do you have any ideas how to fix it?

@justinmk
Copy link
Contributor

@avetokhin I'm pretty sure that should just be a matter of stubbing out Image.getElement(). Something like this (from memory):

@GwtMock Image mockImage;
Mockito.when(mockImage.getElement()).thenReturn(ImageElement.class);

@ajelcocks
Copy link

I am was getting the ClassCastException for CellTable as mentioned 7 months ago.
I tried the solution of subclassing GwtMockitoTestRunner and now I get NoClassDefFound (com/google/gwt/user/cellview/client/CellTable)

Using GwtMockito 1.1.3 from maven repo

Is there a solution to this?

@ekuefler
Copy link
Collaborator

ekuefler commented Jun 8, 2014

Spent a long time trying to find a good workaround for Image (which does a lot of weird things casting Elements back and forth in a way that makes Java unhappy), without much luck. For now I've added it to the list of classes to stub by default, which should fix the issues you describe (but may cause others, let me know if so).

I've also fixed a few issues related to CellTable. You should be able to see those and the Image fixes if you run against the latest snapshot - is anyone still seeing ClassCastExceptions?

@kishorepalakollu
Copy link

Hi, i am getting a classcast exception when writing the Junit testcases for the following scenario.
JsArray overlayArray=(JsArray) JavaScriptObject.createArray().cast();
java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$$EnhancerByMockitoWithCGLIB$$3f37cdf7 cannot be cast to com.google.gwt.core.client.JsArray
at com.siemens.splm.clientfx.kernel.clientmodel.internal.JavaSciptObjectCastHepler.cast(JavaSciptObjectCastHepler.java:55)
at com.siemens.splm.clientfx.kernel.clientmodel.internal.ListHelper.getOverlayArrayFromPojoList(ListHelper.java:61)
at com.siemens.splm.clientfx.kernel.clientmodel.internal.ListHelperTest.testGetOverlayArrayFromPojoListWithNotNullInput(ListHelperTest.java:111)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at com.google.gwtmockito.GwtMockitoTestRunner.run(GwtMockitoTestRunner.java:102)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

ekuefler added a commit that referenced this issue Dec 8, 2014
…n't do

anything about so that we can at least provide an error messages to users
hinting how they can work around the problem.

Refs #4, #22, #43, #47.
@timeu
Copy link

timeu commented Aug 13, 2015

Is there a known workaround for the ClassCastException that @kishorepalakollu reported when dealing with JavascriptObject and JsArray ? Adding them to the @WithClassesToStub didn't really solve it

@kishore-palakollu
Copy link

@timeu Actually i have created a mehtod to mock the JavaScriptObject.createArray() and return the javascriptobject. and i mocked it .and after that i mocked the javascriptobjectobejct.cast and returned the jsarray. and this resolved my issue.

@vikram-github
Copy link

@ekuefler - I still get class cast exceptions in my code when I run GWT mockito test cases. Below is the snippet code which causing the cast issue.
Element testElement = (Element) Document.get().createElement(tag);

Caused by: java.lang.ClassCastException: com.google.gwt.dom.client.Element$$EnhancerByMockitoWithCGLIB$$4ba5b663 cannot be cast to com.google.gwt.user.client.Element.

Any workaround for this issue?

@kishore-palakollu
Copy link

@vikram-github Yes even i faced the same issue,then i created two public methods in my class and
did like this
Element testElement = getElement();
public documentReturntype getDoucment(){
return Document.get();
}
ou
public Element getElement(){
return getDocument().createElement(tag);
}

Now after this ,in your test file use spy Mocikto.spy() and mock the above two methods.
and return the respective return types when these methods are called.
This should work and it wont cause any impact in your application also.

@lynx-r
Copy link

lynx-r commented Nov 6, 2015

@GwtMock Image mockImage;
Mockito.when(mockImage.getElement()).thenReturn(ImageElement.class);

@justinmk do you have a working example?

@khouari1
Copy link

khouari1 commented Sep 6, 2016

Hey, I am experiencing the following class cast exception issue:

Caused by: java.lang.ClassCastException: com.google.gwt.dom.client.Text$$EnhancerByMockitoWithCGLIB$$4050ddf1 cannot be cast to com.google.gwt.dom.client.Element
at org.gwtbootstrap3.client.ui.html.Text.(Text.java:63)
at org.gwtbootstrap3.client.ui.html.Text.(Text.java:53)
at org.gwtbootstrap3.client.ui.Heading.(Heading.java:84)
at org.gwtbootstrap3.client.ui.ModalHeader.(ModalHeader.java:38)
at org.gwtbootstrap3.client.ui.Modal.(Modal.java:98)

@manstis
Copy link

manstis commented Dec 12, 2016

@khouari1 Using @WithClassesToStub({ Text.class } ) works in our cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests