Mocking Method that Takes a Class<?> Type Argument with JMock
Asked Answered
E

2

11

Background:

This is a JMock+JUnit specific question (those are the two technologies I must use). Yes, what I want to do can be done with PowerMock, but this is an edge case that doesn't warrant changing of tools. And no, sorry, I'm not asking this question to debate the philosophical validity of static methods :)

With that out of the way, I will really thank anyone taking a look at this question.

Question:

I have piece of legacy code that I need to write a test for (we are trying to put tests around inherited code to ensure we don't break anything during a potentially massive refactoring effort... that's a tale for another time.)

Goal:

The method I'm trying to mock is the Foo.bar method in the class below using JMock's class imposterizer facility (via JUnit4Mockery.)

The code below is representative to the code I'm test-wrapping:

public class Foo {
     public abstract <T> void bar(
         Class<? extends T> paramClass, T paramT);

My test setup aims to allow any # of calls to bar() receiving a Class instance (which obviously degenerates to Class ... silly Java type erasure "feature"), paired with any instance of Snafu.

That is the key distinction here. I'm not pairing two Matcher parameters, or two literal parameters, but one literal (T.class) and any value of type T. JMock does not allow this, so the expected solution would be to have a Matcher> and a Matcher:

Foo mock = context.mock(Foo.class);
context.checking(new Expectations() {
    // keep warnings close to the culprit code when possible
    @SuppressWarnings("unchecked")
    public void allow(final Foo mockedFoo) {
        allowing(mockedFoo).bar(
            with(any(Snafu.class.getClass())), // Matcher that *should* resolve to Class<?>
            with(any(Snafu.class)));  // matcher to anything of type Snafu.class
    }
    {
        allow(mockedFoo);
    }
});

Then, we inject the mocked Foo, which eventually gets called like this by another class, which I'll refer to as the Driver (*I'll get back to the static method call later):

// fooImpl has been replaced/injected with our mock
fooImpl.bar(Snafu.class, someStaticFunctionThatReturnsASnafu()); 

Problem:

The problem is that when the Driver invokes the bar method on the mocked Foo instance, my test encounters the following exception:

java.lang.IllegalArgumentException: not all parameters were given explicit matchers: either all parameters must be specified by matchers or all must be specified by values, *you cannot mix matchers and values*
    at org.jmock.internal.InvocationExpectationBuilder.checkParameterMatcherCount(InvocationExpectationBuilder.java:98)
    at org.jmock.internal.InvocationExpectationBuilder.createExpectationFrom(InvocationExpectationBuilder.java:91)
    at org.jmock.internal.InvocationToExpectationTranslator.invoke(InvocationToExpectationTranslator.java:19)
    at org.jmock.internal.FakeObjectMethods.invoke(FakeObjectMethods.java:38)
    at org.jmock.lib.legacy.ClassImposteriser$4.invoke(ClassImposteriser.java:129)
    at .....

Apparently (or so it looks to me), JMock matchers' see Class instances as values, regardless of how we try to match them. Or am I missing something?

I'm encountering similar exceptions in many legacy calls that take a java.lang.Class argument. Obviously, anything that looks like X.class will be a value, not a new instance.

But therein lies the problem because the other argument must be resolved with a matcher, not just with an actual value.


[*] Ideally one could rewrite the static method call in

fooImpl.bar(Snafu.class, someStaticFunctionThatReturnsASnafu()); 

with something more amenable to mocking (a non-static method, another object or something injected with a IoC).

Probably that is the way we will eventually go, but for the time being, the code in question has a significant number of static calls.

I would like to defer that till a more appropriate moment, and instead find a general JMock solution, if one exists, that allows me to set the necessary expectations of mocking functions that like Foo.bar above.

Eglantine answered 3/4, 2014 at 15:41 Comment(2)
Is it possible for you, to provide a minimum executable JUnit test class, that reproduces your problem? I just tried to reproduce your problem with no luck - i.e. calling fooImpl.bar(Snafu.class, someStaticFunctionThatReturnsASnafu()); with the context.checking method provided by you didn't throw any exceptions (but I didn't really know where you got the broker argument from)...Thadeus
Ooop, thanks. The broker part is a copy/paste mistake. Instead of allow(broker), it should be allow(mockedFoo). I'll extract code out to make a minimum compilable proof-of-concept to show the error. BTW, the code as-is compiles. It is at run-time that we get the exception.Eglantine
T
5

If I'm not mistaken, you did everything right in your test case. The documentation of JMock about the with clause states

An expectation that uses parameter matchers must use the "with" method to wrap every parameter, whether a matcher function or a literal value.

The important part here is the emphasize on every. You should only get the IllegalArgumentException you mentioned,

java.lang.IllegalArgumentException: not all parameters were given explicit matchers: either all parameters must be specified by matchers or all must be specified by values, you cannot mix matchers and values

if you are mixing a with clause with a literal value - in your case e.g.

allowing(mockedFoo).bar(Class.class, with(any(Snafu.class)));

where Class.class is the literal value. See also here.

I tested your code and it seems to work as expected. Here is my complete JUnit TestCase:

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import junit.framework.TestCase;

public class FooTest extends TestCase{
    Mockery context = new JUnit4Mockery();

    public interface Foo {
        public abstract <T> void bar(Class<? extends T> paramClass, T paramT);
    }

    public static class Snafu {}

    public void testFoo() {
        final Foo mock = context.mock(Foo.class);
        context.checking(new Expectations() {
            // keep warnings close to the culprit code when possible
            @SuppressWarnings("unchecked")
            public void allow(final Foo mockedFoo) {
                allowing(mockedFoo).bar(
                        with(any(Class.class)), // Matcher that *should* resolve to Class<?>
                        with(any(Snafu.class)));  // matcher to anything of type Snafu.class
            }
            {
                allow(mock);
            }
        });

        // test bar method (two invocations)
        mock.bar(Snafu.class, someStaticFunctionThatReturnsASnafu());
        mock.bar(Snafu.class, someStaticFunctionThatReturnsASnafu());

    }

    public static Snafu someStaticFunctionThatReturnsASnafu() {
        return new Snafu();
    }
}

This test case succeeds without any runtime exceptions (tested with JUnit 4 and JMock 2.6.0). I used with(any(Class.class)) instead of with(any(Snafu.class.getClass())) for readability, but it doesn't really matter.

I only get the mentioned IllegalArgumentException, if I change this to

allowing(mockedFoo).bar(Class.class, with(any(Snafu.class)));
Thadeus answered 8/4, 2014 at 3:48 Comment(1)
Thanks. I'm accepting yours as an answer. I cannot tell exactly what is in our actual code base that is causing the error, but when done with a simple proof-of-concept, we can see that the feature works. Oh well.Eglantine
A
1

I've used this because it seems to be the only way I can get as explicit as I want to:

allowing(mockedFoo).bar(
    with(Expectations.<Class<Snafu>>anything()),
    with(any(Snafu.class))
);
Arola answered 7/1, 2017 at 1:8 Comment(1)
@Eglantine there's only one '.', where do you think it should be?Arola

© 2022 - 2024 — McMap. All rights reserved.