3

I have a following snippet with JMock expectations in my test method:

context.checking(new Expectations() {{
    allowing(listener).tableChanged(with(anyInsertionEvent()));
    oneOf(listener).tableChanged(with(aRowChangedEvent(0)));
}});

where anyInsertionEvent and aRowChangedEventAs return instances of Matcher<TableModelEvent>. This is taken from book "Growing Object-Oriented Software Guided by Tests" (p. 181).

I try to convert this test to Groovy, but the method I need:

org.jmock.Expectations.with(org.hamcrest.Matcher<T>)

is shadowed by:

org.codehaus.groovy.runtime.DefaultGroovyMethods.with(java.lang.Object, 
    groovy.lang.Closure<T>)

As a result I get an error during my tests like:

groovy.lang.MissingMethodException: No signature of method: $Proxy8.tableChanged() is     applicable for argument types: (java.lang.Boolean) values: [false]
Possible solutions: tableChanged(javax.swing.event.TableModelEvent)
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:55)
    at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:46)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at org.rorick.auctionsniper.ui.SnipersTableModelTest$1.<init>(SnipersTableModelTest.groovy:43)
    at org.rorick.auctionsniper.ui.SnipersTableModelTest.setSniperValuesInColumns(SnipersTableModelTest.groovy:42)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:66)
    at org.jmock.integration.junit4.JMock$1.invoke(JMock.java:37)
    at org.junit.internal.runners.MethodRoadie.runTestMethod(MethodRoadie.java:105)
    at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:86)
    at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:94)
    at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:84)
    at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:49)
    at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:98)
    at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:61)
    at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:54)
    at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
    at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
    at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:52)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76)
    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:57)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

How can I use appropriate with method? Or, please, advice any other way to resolve this issue.

UPDATE: it is not shadowing. The method actually called is Expectations.with(Matcher<Boolean>) hence false value in stack-trace. So, method is incorrectly dispatched. Any ideas what to do with that?

UPDATE: Matcher method are the following:

public Matcher<TableModelEvent> anyInsertionEvent() {
    Matcher<Integer> insertMatcher = equalTo(TableModelEvent.INSERT);
    return new FeatureMatcher<TableModelEvent, Integer>(insertMatcher, "is an insertion event", "event type") {
        @Override
        protected Integer featureValueOf(TableModelEvent actual) {
            return actual.getType();
        }
    };
}

private Matcher<TableModelEvent> aRowChangedEvent(int row) {
    return samePropertyValuesAs(new TableModelEvent(model, row));
}
Rorick
  • 8,857
  • 3
  • 32
  • 37
  • I'm not convinced you are interpreting your error message correctly. You say that method shadowing is causing the problem, yet the error message seems to be complaining about the type returned by the matcher. Can you post the complete error message? – Duncan Jones Sep 05 '12 at 12:08
  • Maybe it is not shadowing. But this is complete error message from maven log. And exactly this code perfectly works under Java, I just renamed file. So I guess types are OK: `Expectations.with` takes `Matcher` and returns `T`; `anyInsertionEvent`returns `Matcher` and `tableChanged` expects `TableModelEvent`. I'll see if I will be able to give more details later. Right now I have no access to that code. – Rorick Sep 05 '12 at 12:35
  • Can we see the definitions of `anyInsertionEvent` and `aRowChangedEventAs` and the part of the test that actually triggers the expectations? Also, has this ever worked in pure Java? – Duncan Jones Sep 05 '12 at 20:36
  • See update. Yes, it works perfectly just by renaming file to java. – Rorick Sep 05 '12 at 20:44
  • Think, that these are related: http://stackoverflow.com/questions/2008291/generics-in-groovy?rq=1 http://stackoverflow.com/questions/3897245/groovy-static-generic-type http://stackoverflow.com/questions/10932288/java-generics-and-overloading-with-groovy – Rorick Sep 05 '12 at 20:46
  • So, seems like Groovy is just not able to distinguish `with(Matcher)` from `with(Matcher)` from `with(Matcher)` etc. – Rorick Sep 05 '12 at 20:49
  • I guess pure Java would be the same - type erasure is a tricky devil. Is this now resolved in your eyes? – Duncan Jones Sep 05 '12 at 21:02
  • As I said, pure Java easily distinguishes those methods, and Java test passes successfully. Groovy seems to erase generics even before compilations as stated in http://groovy.codehaus.org/Generics so it has no chance to differentiate them. I figured out a kind of workaround, though I was urged to replace `with` with self-made `argThat`. – Rorick Sep 05 '12 at 21:59
  • Struggling with the book at this point just using Java (although with AssertJ and Mockito, not Hamcrest and jMock). Your `aRowChangedEvent` above seems not to do what the book asks... i.e. check precisely one occurrence of a "row changed" event. Instead it seems to indicate the number of ***all*** `TableModelEvents`. Any suggestions how to implement the thing in Java (assuming you cracked this in Java)? – mike rodent Oct 11 '16 at 14:16

1 Answers1

1

Finally, I found a workaround for this. See the checking code below. new Expectations() {{}} Java syntax has gone and is replaced with closure:

context.checking {
    allowing(listener).tableChanged(argThat(anyInsertionEvent()));
    oneOf(listener).tableChanged(argThat(aRowChangedEvent(0)));
}

Note that with is replaced with argThat (modeled after Mockito). context is not an instance of org.jmock.integration.junit4.JUnit4Mockery as it were, but is an instance of the following class:

class JUnit4GroovyMockery extends JUnit4Mockery {
    public void checking(Closure c) {
         ClosureExpectations expectations = new ClosureExpectations();
         expectations.closureInit(c, expectations);
         super.checking(expectations);
    }

    private static class ClosureExpectations extends Expectations {
        void closureInit(Closure cl, Object delegate) {
            cl.setDelegate(delegate);
            cl.call();
        }

        public Object argThat(Matcher<?> matcher) {
            currentBuilder().addParameterMatcher(matcher);
            return null;
        }
    }
}

This is based on JUnit4GroovyMockery class from http://docs.codehaus.org/display/GROOVY/Using+JMock+with+Groovy. argThat is copy-pasted from Expectations.with(Matcher<T>) and Expectations.addParameterMatcher(Matcher<?>).

This works for me, though I'm not very pleased with this solution; and I'd prefer to keep with name for the method.

Rorick
  • 8,857
  • 3
  • 32
  • 37