Open konradstrack opened 11 years ago
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!
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 ce7323e5aab66f63342ba9065c91713a8bb06d7f. Want to try building GwtMockito from HEAD and seeing if that solves your problem?
Thank you for the very detailed answer. Unfortunately, the problem has been solved only partially.
First of all, I can successfully build commit ce7323e5aab66f63342ba9065c91713a8bb06d7f but the latest one (8f7f4afed768df2593319f7aa9f2e62b4735e555) 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 ce7323e5aab66f63342ba9065c91713a8bb06d7f, 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)
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 11cb40a251da0c159c4f088bd4c9b106597a1c18. Let me know if that helps with your use case.
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.
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?
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.
Okay, I've provided a protected method that you can override in 2e9f9f44d13b5d9c4599d4df0ab9acf58dc61dab. Check it out and let me know if it works like you want it to.
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.
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.
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);
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.
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?
There is a further issue with the suggested workaround in https://github.com/google/gwtmockito/issues/4#issuecomment-18729283
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
@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.
Thanks @ekuefler, I will try that!
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?
@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);
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?
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?
Hi, i am getting a classcast exception when writing the Junit testcases for the following scenario.
JsArray
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
@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.
@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?
@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.
@GwtMock Image mockImage;
Mockito.when(mockImage.getElement()).thenReturn(ImageElement.class);
@justinmk do you have a working example?
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.
@khouari1 Using @WithClassesToStub({ Text.class } )
works in our cases.
Let's take the following example:
And a test case:
Running this test case will result in the following exception:
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?