mlinhard / mockito

Automatically exported from code.google.com/p/mockito
0 stars 0 forks source link

Mockito fails to detect call of sub-classes of scala.swing.Component #271

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
I have been using Mockito with Specs2, and recently some test that worked on 
Scala 2.8.1 stopped working on Scala 2.9.0.1. 

We discussed this over at the Specs2 side, but something in the internal of 
Mockito is not liking the scala.swing.Component on version 2.9.0.1.

I created this Java test case, that mocks a scala.swing.Button (which wraps a 
JButton).

import junit.framework.TestCase;
import scala.swing.Button;

import static org.mockito.Mockito.*;

public class ScalaSwingTeste extends TestCase {
    public void testScalaButton() {
        Button mButton = mock(Button.class);
        mButton.doClick();
        verify(mButton).doClick();
    }
}

I would expect that his test pass (as it does with Scala 2.8.1). On Scala 2.9.1 
I get:

Argument(s) are different! Wanted:
button.doClick();
-> at ScalaSwingTeste.testScalaButton(ScalaSwingTeste.java:17)
Actual invocation has different arguments:
button.doClick();
-> at ScalaSwingTeste.testScalaButton(ScalaSwingTeste.java:16)

Expected :button.doClick();
Actual   :button.doClick();

I suspected that the Proxy trait that overrides the equals methods could be the 
culprit, to I tried to mock the peer() method just in case:

    public void testScalaButton2() {
        Button mButton = mock(Button.class);
        JButton mJButton = mock(JButton.class);
        when(mButton.peer()).thenReturn(mJButton);
        mButton.doClick();
        verify(mButton).doClick();
    }

Works with Scala 2.8.1 and on 2.9.0.1 I get: 

java.lang.StackOverflowError
    at java.util.SubList.checkForComodification(AbstractList.java:751)
    at java.util.SubList.size(AbstractList.java:625)
    at java.util.AbstractCollection.toArray(AbstractCollection.java:119)
    at java.util.ArrayList.addAll(ArrayList.java:472)

There is something failing on the invocation matching. There is definitely some 
change on the Scala side, but I cant really figure out what is causing the 
failure. If I can get some information on what is causing the failure there may 
be option to fix the on the Scala side.

Original issue reported on code.google.com by maill...@gmail.com on 15 Jul 2011 at 12:37

GoogleCodeExporter commented 9 years ago
I am not a scala guy so I dont think I have time to debug it any time soon. 
However i suspect:
-final methods
-change to doReturn syntax for stubbing - might be easier to debug
-bridge methods. We have some open issues related. Perhaps some interfaces 
changed in a way it incurrs more/less bridge methods involved in stubbing 
verification.

Can you submit full stacktraces?

HTH!

Original comment by szcze...@gmail.com on 15 Jul 2011 at 12:09

GoogleCodeExporter commented 9 years ago
I am not a scala guy so I dont think I have time to debug it any time soon. 
However i suspect:
-final methods
-change to doReturn syntax for stubbing - might be easier to debug
-bridge methods. We have some open issues related. Perhaps some interfaces 
changed in a way it incurrs more/less bridge methods involved in stubbing 
verification.

Can you submit full stacktraces?

HTH!

Original comment by szcze...@gmail.com on 15 Jul 2011 at 12:09

GoogleCodeExporter commented 9 years ago
I'm attaching the surefire reports and a simple Maven project that allows you 
to reproduce the problem. The pom.xml has a property for the Scala version. If 
you change from 2.9.0-1 to 2.8.1 (this one is commented on the file). 

This should allow you to run. It's all java (except for the library).

Original comment by maill...@gmail.com on 15 Jul 2011 at 12:44

Attachments:

GoogleCodeExporter commented 9 years ago
I was wondering if this example helped, and also what I should be looking for 
in terms of "bridge methods". What should I be looking for?

With regards of doReturn, what exactly should I do? I used Mockito directly 
very little.

Original comment by maill...@gmail.com on 21 Jul 2011 at 6:10

GoogleCodeExporter commented 9 years ago
Sorry, I haven't yet looked at the samples you provided. Will do it at some 
point but not sure when :)

Instead of when(foo.bar()).thenReturn('x') you can do: 
doReturn('x').when(foo).bar()

Cheers!

Original comment by szcze...@gmail.com on 22 Jul 2011 at 8:27

GoogleCodeExporter commented 9 years ago
I did some additional testing. doReturn does not help at all. However looking 
at the stack-frames and some debugging this area called my attention. 

{{{
    at scala.swing.Component.equals(Component.scala:45)
    at org.mockito.internal.invocation.InvocationMatcher.matches(InvocationMatcher.java:57)
}}}

Turns out that:

{{{
Button mButton = mock(Button.class);
assertTrue(mButton.equals(mButton));
}}}

Fails with:

{{{
junit.framework.AssertionFailedError: null
    at ScalaSwingTest.testMockMatch(ScalaSwingTest.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
}}}

The mock in questions hash a field call self, and it is null in the mock. An 
the comparison is done as follows:
{{{
  def equals(that: Any): Boolean = that match {
    case null       => false
    case x: Equals  => (x canEqual self) && (x equals self)
    case x          => (x equals self) // THIS IS THE TEST
  }
}}}

Original comment by maill...@gmail.com on 11 Aug 2011 at 1:50

GoogleCodeExporter commented 9 years ago
Did some more testing and found that probably equals is the culprit. I defined 
this class:

    class MyButton extends Button {
        public MyButton(Action a) {
            super(a);
        }
        @Override public boolean equals(Object o) {
            System.out.println("Super equals would be: " + super.equals(o));
            return o != null && o instanceof MyButton && (this == o);
        }
    }

And added this test:

    public void testMockMatchCustomEquals() {
        Button mButton = mock(MyButton.class);
        mButton.doClick();
        verify(mButton).doClick();
    }

Which passes.

Also when I compile the project I get the following message:

equals(java.lang.Object) in ScalaSwingTest.MyButton overrides 
equals(java.lang.Object) in scala.swing.Component; overridden method is a 
bridge method

So I think there is a bridge method. I do believe this is a problem on the 
scala side. But I'd like to know it there any work-around on Mockito. On thing 
I thought was having a way to make equals compare using ==. 

Something like:

mockWithoutEquals(Button.class)

or 

Button button = mock(Button.class)
forMock(button).ignoreEquals(true)

The point is making the mock ignore equals for certain cases.

Original comment by maill...@gmail.com on 15 Aug 2011 at 2:03

GoogleCodeExporter commented 9 years ago
Hi, thx for your analysis. However I don't think we could do something on our 
part.

Mocks are stored in collections internally which use the couple 
equals/hashcode. We do not offer mocking support for these methods as we need 
them.

I think it's safe to assume we can't do anything on this matter without 
triggering another set of issues, especially if things broke when changing the 
version of Scala. I'll put this issue in WontFix status.

Original comment by brice.du...@gmail.com on 3 Oct 2011 at 10:33

GoogleCodeExporter commented 9 years ago
I agree. One work around that could work is to have a mock that does not use 
equals to match but instead the == operator (same object check). This could 
make it possible to mock these ill behaved objects. 

Is there a way to extend Mockito.mock easily to do this?

Original comment by maill...@gmail.com on 4 Oct 2011 at 9:27

GoogleCodeExporter commented 9 years ago

Original comment by brice.du...@gmail.com on 6 Sep 2012 at 3:45