Vladimir Makhnovsky
Vladimir Makhnovsky

Reputation: 291

Mockito - stub abstract parent class method

I am seeing very strange behavior trying to stub a method myMethod(param) of class MyClass that is defined in an abstract parent class MyAbstractBaseClass.

When I try to stub (using doReturn("...").when(MyClassMock).myMethod(...) etc.) this method fails, different exceptions are thrown under different scenarios. The exceptions are thrown right on that line.

When I use doReturn("...").when(MyClassMock).myMethod(CONCRETE PARAM CLASS OBJECT), I get the following exception:

org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 
String cannot be returned by hasValidExpirationDate()
hasValidExpirationDate() should return boolean
    at ...

hasValidExpirationDate() is not a method being stubbed, but it is called by the real implementation of MyMethod(param) in the abstract base class.

When I use doReturn("...").when(MyClassMock).myMethod(any(PARAMCLASS.class)), I get the following exception:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Invalid use of argument matchers!
0 matchers expected, 1 recorded.
This exception may occur if matchers are combined with raw values:

, etc.

But when I define the method myMethod(param) in a subclass MyClass the code no longer fails. My concrete implementation in MyClass just calls super.myMethod(param) and returns it, so it has no effect other than fixing the unit test. So it looks like Mockito can only stub methods defined in the class being mocked itself, not in the super classes.

I am reading Mockito documentation and I don't see where it says that inherited methods can't be stubbed.

myMethod(param) is neither static nor final.

Code:

Class BaseCard:

import java.io.Serializable;

public class BaseCard implements Serializable {

    public boolean hasValidExpirationDate() {
        return true;
    }
}

Class Card:

abstract class Card  extends BaseCard {

    public Card () {    }

    public String getUnexpiredStringForNetwork(){

        //If the date is invalid return empty string, except for Discover.
        if( ! hasValidExpirationDate()){
           return "hi";
        }

        return "hello";
    }
}

Class DecryptedCard:

public class DecryptedCard extends Card {

}

Class MyTest:

import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

import org.junit.Test;


public class MyTest {

    @Test
    public void test() {
        DecryptedCard decryptedCardMock = mock(DecryptedCard.class);

        doReturn("ABC").when(decryptedCardMock).getUnexpiredStringForNetwork();

    }

}

Failure:

org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 
String cannot be returned by hasValidExpirationDate()
hasValidExpirationDate() should return boolean
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - 
   - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

    at Card.getUnexpiredStringForNetwork(Card.java:10)
    at DecryptedCard.getUnexpiredStringForNetwork(DecryptedCard.java:1)
    at MyTest.test(MyTest.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

Upvotes: 4

Views: 3747

Answers (2)

Kamil Orzechowski
Kamil Orzechowski

Reputation: 124

Wow! That was it!

I was getting strange exception:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Misplaced argument matcher detected here:

-> at com.medziku.motoresponder.logic.ExposedResponder.createSettings(ResponderTest.java:163)
-> at com.medziku.motoresponder.logic.ExposedResponder.createSettings(ResponderTest.java:163)

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

While everything was correct. I tried to mock method from superclass of mock class, and this superclass was inside same file as mock class, so it was not public and those strange errors arise. after moving superclass to separate file, and setting it public, problem is gone!

Upvotes: 0

Jeff Bowman
Jeff Bowman

Reputation: 95644

According to this SO answer, mock behavior isn't guaranteed when parent class is non-public, as documented in issue 212.

(Thanks Brice for the good answer in the other thread, and thanks Vladimir, JB Nizet, and acdcjunior for sharing debugging progress in the comments thread!)

Upvotes: 1

Related Questions