5

I have written a custom Hamcrest Matcher<Double> to use with Mockito.doubleThat.

I want to "override the toString()" method, so to speak, so that if there's a failure, the error is more verbose. Here's the JUnit failure trace:

Argument(s) are different! Wanted:
dependedOnComponent.method(
    <Double matcher>
);
-> at my.domain.TestClass.testMethod(TestClass.java:123)
Actual invocation has different arguments:
dependedOnComponent.method(
    123.45,
);
-> at my.domain.SystemUnderTest.callingMethod(SystemUnderTest.java:456)

As you can see, it prints <Double matcher>. Is it possible to override that message? Instead, I would like to see, as an example:

Argument(s) are different! Wanted:
dependedOnComponent.method(
    120 < matcher < 121
);

But a different instantiantion of my matcher class might be:

Argument(s) are different! Wanted:
dependedOnComponent.method(
    1 < matcher < 200
);

I don't need to know how to write the code to generate the numbers or the syntax, I just need to know WHERE to put it.

durron597
  • 31,968
  • 17
  • 99
  • 158

2 Answers2

3

So I was doing something silly; I was reading the Javadoc for Matcher when I really should have been looking at the Javadoc for ArgumentMatcher.

Once I realized my mistake, it's easy; just override the describeTo method defined in that interface, e.g.

@Override
public void describeTo(Description description) {
    description.appendText(String.valueOf(expected));
    description.appendText(" ± ");
    description.appendText(String.valueOf(delta));
}
durron597
  • 31,968
  • 17
  • 99
  • 158
  • IIRC, the expectation is that you'll extend `CustomMatcher` (as per my answer) rather than `ArgumentMatcher`. It would be impossible to make this "mistake" when extending that class, because the constructor forces you to define the description. – Duncan Jones May 23 '14 at 14:41
  • Having written the above comment, I see that the Javadocs for `ArgumentMatcher` somewhat disagree with me. Oh well :-) – Duncan Jones May 23 '14 at 14:42
  • @Duncan Yah it's a Mockito thing, not a JUnit thing ;) – durron597 May 23 '14 at 14:43
  • 1
    It seems strange that they've created a class that is essentially the same as Hamcrest's `CustomMatcher` class. I wonder why they've done that - perhaps just to avoid people declaring a dependency upon Hamcrest. – Duncan Jones May 23 '14 at 14:44
  • @Duncan [Mockito depends upon Hamcrest...](http://search.maven.org/#artifactdetails%7Corg.mockito%7Cmockito-core%7C1.9.5%7Cjar) Note that's mockito-core not mockito-all (translation: I don't know either) – durron597 May 23 '14 at 14:45
  • Ah, mystery solved. The latest Mockito is built against Hamcrest 1.1, which doesn't contain the `CustomMatcher` class. So you would have to explicitly declare a dependency on a newer Hamcrest library for my solution to work. +1 to your solution for being the "Mockito way" :-) – Duncan Jones May 23 '14 at 14:48
  • @Duncan This is a good time for me to [post a link to this question I posted last year](http://stackoverflow.com/questions/18770943/mockito-junit-hamcrest-versioning?rq=1) – durron597 May 23 '14 at 14:48
2

Try the following:

@RunWith(MockitoJUnitRunner.class)
public class SOTest {

    public interface Foo {
        void doSomething(double d);
    }

    @Test
    public void sillyTest() {

        Foo foo = mock(Foo.class);
        foo.doSomething(1);
        verify(foo).doSomething(Mockito.doubleThat(new TestMatcher(2)));
    }

    public class TestMatcher extends CustomMatcher<Double> {

        private final double expected;

        public TestMatcher(double expected) {
            super(Double.toString(expected));
            this.expected = expected;
        }

        @Override
        public boolean matches(Object item) {
            return item instanceof Double && ((Double) item).equals(expected);
        }
    }
}

Output:

Argument(s) are different! Wanted:
foo.doSomething(2.0);
-> at SOTest.sillyTest(SOTest.java:23)
Actual invocation has different arguments:
foo.doSomething(1.0);
-> at SOTest.sillyTest(SOTest.java:22)
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
  • Sorry, I should have made clear that I wanted the message to change based on the different instantiations – durron597 May 23 '14 at 14:15
  • Thanks for your help, I ended up doing what I described in my answer. However, you definitely put me on the right track (because I realized that I wasn't implementing `Matcher`, I was extending `ArgumentMatcher`), so I gave you an upvote :) – durron597 May 23 '14 at 14:32