Mathew Alden
Mathew Alden

Reputation: 1670

Powermock unexpectedly throwing InvalidUseOfMatchersException on final void method

I'm working to unit test my Java app..

My goal is to use Powermock to create a spy on an instance of the BOProcessor class. BOProcessor has a final void method; I will setup my spy to throw an exception when this method is called. I will also be mocking MyDao in this same test, but mocking this class is straightforward. The mocked MyDao will then be passed into an instance of MyDaoService named classUnderTest. I will then make assertions against classUnderTest.

Whenever I try to setup the above scenario, Powermock (or Mockito?) throws an InvalidUseOfMatchersException when I setup the doThrow on my spy. Strangely, this exception is only thrown when the doThrow expectation is followed by a call to classUnderTest. If I remove the later call the classUnderTest, the expectation works fine. Even weirder - classUnderTest doesn't even use the spy that is throwing the error!

This is the entirety of my test code outlined above. To highlight the problem, I've removed all code not directly related. (I've even removed the whole purpose of this test.)

package my.package;

import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doThrow;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;

import org.junit.Test;

public class WhatAmIDoingWrong {

    @Test
    public void whatAmIDoingWrong() {

        MyDao mockedDao = mock(MyDao.class);
        BOProcessor processor = new BOProcessor();
        BOProcessor mockedProcessor = spy(processor);

        MyDaoService classUnderTest = new MyDaoService(mockedDao);

        doThrow(new Exception()).when(mockedProcessor).process(any(FakeBusinessObject.class));

        classUnderTest.interactWithDao();
    }
}

Here is the exception - thrown (ironically) from the doThrow line of my test code - which I'm trying to solve.

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
0 matchers expected, 1 recorded:
-> at my.package.WhatAmIDoingWrong.whatAmIDoingWrong(WhatAmIDoingWrong.java:21)

This exception may occur if matchers are combined with raw values:
    //incorrect:
    someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
    //correct:
    someMethod(anyObject(), eq("String by matcher"));

For more info see javadoc for Matchers class.

    at my.package.MyDaoService.interactWithDao(MyDaoService.java:33)
    at my.package.WhatAmIDoingWrong.whatAmIDoingWrong(WhatAmIDoingWrong.java:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

Here are the classes used by my test. To reiterate, the MyDaoService named classUnderTest doesn't even know about the spy of BOProcessor; it only works against the mock of MyDao. But the expectations on the BOProcessor spy only fail if the classUnderTest is called.

public class BOProcessor {

    public final void process(FakeBusinessObject bar) {}
}
public class FakeBusinessObject {

}
import java.util.Collections;
import java.util.List;

public class MyDao {

    public MyDao() {}

    public List<String> getAllData(){
        return Collections.emptyList();
    }

}
public class MyDaoService {

    private MyDao applicationDao;

    public MyDaoService(MyDao applicationDao) {
        this.applicationDao = applicationDao;
    }

    public synchronized void interactWithDao() {
        applicationDao.getAllData();
    }
}

I'm usin JUnit 4.12, Mockito 1.10.19, and Powermock 1.7.4. The project is running Spring 4.3.12RELEASE with spring-test included.

Why is Powermock throwing this exception? Am I not using the any Matcher correctly? Why on earth is this exception only thrown when a later call interacts with a different mock?

Thanks for the help!

Upvotes: 0

Views: 272

Answers (1)

Mathew Alden
Mathew Alden

Reputation: 1670

It turns out that I was using Spies wrong. Something in the way org.mockito.stubbing.Stubber.when(T mock) is implemented means I cannot set expectations on the Spy the way I wanted to. But a Capture was actually a better fit for my use case anyway.

In the end, my test looked like this:

public class FixedNow{

    @Test
    public void fixedNow() {

        MyDao mockedDao = mock(MyDao.class);
        BOProcessor mockedProcessor = mock(BOProcessor.class);
        FakeBusinessObject problematicBO = new FakeBusinessObject();
        ArgumentCaptor<FakeBusinessObject> fakeBOCaptor = ArgumentCaptor.forClass(FakeBusinessObject.class);

        MyDaoService classUnderTest = new MyDaoService(mockedDao, mockedProcessor);

        doThrow(new Exception()).when(mockedProcessor).process(eq(problematicBO));
        doNothing().when(mockedProcessor).process(fakeBOCaptor.capture());

        classUnderTest.interactWithDao();

        assertThings(BOCaptor.getValue());
    }
}

Thanks for your thoughts!

Upvotes: 0

Related Questions