简体   繁体   中英

Final method mocking

I need mock some class with final method using mockito. I have wrote something like this

@Test
public void test() {
    B b = mock(B.class);
    doReturn("bar called").when(b).bar();   
    assertEquals("must be \"overrided\"", "bar called", b.bar());
    //bla-bla
}


class B {
    public final String bar() {
        return "fail";
    }
}

But it fails. I tried some "hack" and it works.

   @Test
   public void hackTest() {
        class NewB extends B {
            public String barForTest() {
                return bar();
            }
        }
        NewB b = mock(NewB.class);
        doReturn("bar called").when(b).barForTest();
        assertEquals("must be \"overrided\"", "bar called", b.barForTest());
    }

It works, but "smells".

So, Where is the right way?

Thanks.

There is no support for mocking final methods in Mockito.

As Jon Skeet commented you should be looking for a way to avoid the dependency on the final method. That said, there are some ways out through bytecode manipulation (eg with PowerMock)

A comparison between Mockito and PowerMock will explain things in detail.

From the Mockito FAQ :

What are the limitations of Mockito

  • Cannot mock final methods - their real behavior is executed without any exception. Mockito cannot warn you about mocking final methods so be vigilant.

You can use Powermock together with Mockito, then you do not need to subclass B.class. Just add this to the top of your test class

@RunWith(PowerMockRunner.class)
@PrepareForTest(B.class)

@PrepareForTest instructs Powermock to instrument B.class to make the final and static methods mockable. A disadvantage of this approach is that you must use PowerMockRunner which precludes use of other test runners such as the Spring test runner.

Mockito 2 now supports mocking final methods but that's an "incubating" feature. It requires some steps to activate it which are described here: https://github.com/mockito/mockito/wiki/What's-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Mockito 2.x now supports final method and final class stubbing.

From the docs :

Mocking of final classes and methods is an incubating, opt-in feature. This feature has to be explicitly activated by creating the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing a single line:

mock-maker-inline

After you create this file you can do:

 final class FinalClass { final String finalMethod() { return "something"; } } FinalClass concrete = new FinalClass(); FinalClass mock = mock(FinalClass.class); given(mock.finalMethod()).willReturn("not anymore"); assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());

In subsequent milestones, the team will bring a programmatic way of using this feature. We will identify and provide support for all unmockable scenarios.

Assuming that B class is as below:

class B {
    private String barValue;
    public final String bar() {
        return barValue;
    }
    public void final setBar(String barValue) {
        this.barValue = barValue;
    }
}

There is a better way to do this without using PowerMockito framework. You can create a SPY for your class and can mock your final method. Below is the way to do it:

@Test
public void test() {

    B b  = new B();
    b.setBar("bar called") //This should the expected output:final_method_bar()
    B spyB = Mockito.spy(b);
    assertEquals("bar called", spyB.bar());

}

Mockito can be used to mock final classes or final methods. The problem is, this doesn't come as out of the box feature from Mockito and needs to be configured explicitely.

So, in order to do that,

Create a text file named org.mockito.plugins.MockMaker to the project's src/test/resources/mockito-extensions directory and add a single line of text as below

mock-maker-inline

Once done, you can use the mockito's when method to mock the behaviour like any other regular method.

See detailed examples here

I just did this same thing. My case was that I wanted to ensure the method didn't "Cause" an error but since it's a catch/log/return method I couldn't test for it directly without modifying the class.

I wanted to simply mock the logger I passed in, but something about mocking the "Log" interface didn't seem to work and Mocking a class like "SimpleLog" didn't work because those methods are final.

I ended up creating an anonymous inner class extending SimpleLog that overrid the base-level "log(level, string, error)" method that the others all delegate to, then just waiting for a call with a "level" of 5.

In general, extending a class for behavior isn't really a bad idea, might be preferable to mocking anyway if it's not too complicated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM