Karol123
Karol123

Reputation: 11

Mockito calls real code in final method instead of throwing MissingMethodInvocationException

In the code below I expect that given will throw MissingMethodInvocationException as foo() is final.

But instead I get NullPointerException at str.equals("String 1").
So, Mockito is calling real code. Why?

import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

class Foo {

    private String str = "String 1";

    public final String foo() {
        if (str.equals("String 1")) {
            str = "String 2";
        }
        return str;
    }
}

@RunWith(MockitoJUnitRunner.class)
public class TestClass {

    @Mock
    Foo foo;

    @Test
    public void test() {
        given(foo.foo()).willReturn("x");
        assertEquals("x", foo.foo());
    }
}

In the example below, I removed the if clause. Now it works as expected.
Of course I don't want to remove those lines as they are needed in the code I'm testing.
How is presence of that lines affecting Mockito's behaviour?

    public final String foo() {
        return str;
    }

How can I make sure that Mockito will never call real code on methods even if they happen to be final?
I'd rather see MissingMethodInvocationException.

And in this code, the test passes:

    public String foo() {
        if (str.equals("String 1")) {
            str = "String 2";
        }
        return str;
    }

The reason I am asking is that I have a test case and someone added final modifier to one of the methods being tested/mocked.
Instead of seeing MissingMethodInvocationException we saw some unrelated Exceptions thrown from 'real' code inside mocked method. We spent some time looking for the place and change that caused tests to fail.
If Mockito threw MissingMethodInvocationException we would see the reason instantly.

Upvotes: 1

Views: 1168

Answers (2)

bric3
bric3

Reputation: 42223

tl;dr : Mockito cannot mock final methods. And it cannot detect a final method being called either.

Longer explanation : It's one of the drawback of finals in Java. The only option you have is to use Powermock, although I would keep that use for legacy code.

The thing with Mockito is that it works by subclassing the type to mock, and the Java compiler or the JVM just won't allow anything that subclass a final class or override a final method. Mockito cannot do anything on that with our current design.

Whereas the nifty Powermock adds another step of reloading the classes in another classloader performing several modifications and especially removing the final flags in the type to mock. Though this approach has drawbacks as well : more configuration in the test, test consumes measurably more Permgen, test is slower to bootstrap.

Upvotes: 3

Related Questions