How to mock a final class with mockito

2019-01-01 15:43发布

问题:

I have a final class, something like this:

public final class RainOnTrees{

   public void startRain(){

        // some code here
   }
}

I am using this class in some other class like this:

public class Seasons{

   RainOnTrees rain = new RainOnTrees();

   public void findSeasonAndRain(){

        rain.startRain();

    }
}

and in my JUnit test class for Seasons.java I want to mock the RainOnTrees class. How can I do this with Mockito?

回答1:

Mocking final/static classes/methods is possible with Mockito v2 only.

This is not possible with Mockito v1, from the Mockito FAQ:

What are the limitations of Mockito

  • Needs java 1.5+

  • Cannot mock final classes

...



回答2:

Mockito 2 now supports final classes and methods!

But for now that\'s an \"incubating\" feature. It requires some steps to activate it which are described in What\'s New in Mockito 2:

Mocking of final classes and methods is an incubating, opt-in feature. It uses a combination of Java agent instrumentation and subclassing in order to enable mockability of these types. As this works differently to our current mechanism and this one has different limitations and as we want to gather experience and user feedback, this feature had to be explicitly activated to be available ; it can be done via the mockito extension mechanism by creating the file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing a single line:

mock-maker-inline

After you created this file, Mockito will automatically use this new engine and one 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. Stay tuned and please let us know what you think of this feature!



回答3:

You cannot mock a final class with Mockito, as you can\'t do it by yourself.

What I do, is to create a non-final class to wrap the final class and use as delegate. An example of this is TwitterFactory class, and this is my mockable class:

public class TwitterFactory {

    private final twitter4j.TwitterFactory factory;

    public TwitterFactory() {
        factory = new twitter4j.TwitterFactory();
    }

    public Twitter getInstance(User user) {
        return factory.getInstance(accessToken(user));
    }

    private AccessToken accessToken(User user) {
        return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
    }

    public Twitter getInstance() {
        return factory.getInstance();
    }
}

The disadvantage is that there is a lot of boilerplate code; the advantage is that you can add some methods that may relate to your application business (like the getInstance that is taking a user instead of an accessToken, in the above case).

In your case I would create a non-final RainOnTrees class that delegate to the final class. Or, if you can make it non-final, it would be better.



回答4:

Use Powermock. This link shows, how to do it: https://github.com/jayway/powermock/wiki/MockFinal



回答5:

Just to follow up. Please add this line to your gradle file:

testCompile group: \'org.mockito\', name: \'mockito-inline\', version: \'2.8.9\'

I have tried various version of mockito-core and mockito-all. Neither of them work.



回答6:

add this in your gradle file:

testCompile \'org.mockito:mockito-inline:2.13.0\'

this is a configuration to make mockito work with final classes



回答7:

I guess you made it final because you want to prevent other classes from extending RainOnTrees. As Effective Java suggests (item 15), there\'s another way to keep a class close for extension without making it final:

  1. Remove the final keyword;

  2. Make its constructor private. No class will be able to extend it because it won\'t be able to call the super constructor;

  3. Create a static factory method to instantiate your class.

    // No more final keyword here.
    public class RainOnTrees {
    
        public static RainOnTrees newInstance() {
            return new RainOnTrees();
        }
    
    
        private RainOnTrees() {
            // Private constructor.
        }
    
        public void startRain() {
    
            // some code here
        }
    }
    

By using this strategy, you\'ll be able to use Mockito and keep your class closed for extension with little boilerplate code.



回答8:

I had the same problem. Since the class I was trying to mock was a simple class, I simply created an instance of it and returned that.



回答9:

Give this a try:

Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));

It worked for me. \"SomeMockableType.class\" is the parent class of what you want to mock or spy, and someInstanceThatIsNotMockableOrSpyable is the actual class that you want to mock or spy.

For more details have a look here



回答10:

Another workaround, which may apply in some cases, is to create an interface that is implemented by that final class, change the code to use the interface instead of the concrete class and then mock the interface. This lets you separate the contract (interface) from the implementation (final class). Of course, if what you want is really to bind to the final class, this will not apply.



回答11:

Actually there is one way, which I use for spying. It would work for you only if two preconditions are satisfied:

  1. You use some kind of DI to inject an instance of final class
  2. Final class implements an interface

Please recall Item 16 from Effective Java. You may create a wrapper (not final) and forward all call to the instance of final class:

public final class RainOnTrees implement IRainOnTrees {
    @Override public void startRain() { // some code here }
}

public class RainOnTreesWrapper implement IRainOnTrees {
    private IRainOnTrees delegate;
    public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
    @Override public void startRain() { delegate.startRain(); }
}

Now not only can you mock your final class but also spy on it:

public class Seasons{
    RainOnTrees rain;
    public Seasons(IRainOnTrees rain) { this.rain = rain; };
    public void findSeasonAndRain(){
        rain.startRain();
   }
}

IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();


回答12:

Yes same problem here, we cannot mock a final class with Mockito. To be accurate, Mockito cannot mock/spy following:

  • final classes
  • anonymous classes
  • primitive types

But using a wrapper class seems to me a big price to pay, so get PowerMockito instead.



回答13:

This can be done if you are using Mockito2, with the new incubating feature which supports mocking of final classes & methods.

Key points to note:
1. Create a simple file with the name “org.mockito.plugins.MockMaker” and place it in a folder named “mockito-extensions”. This folder should be made available on the classpath.
2. The content of the file created above should be a single line as given below:
mock-maker-inline

The above two steps are required in order to activate the mockito extension mechanism and use this opt-in feature.

Sample classes are as follows:-

FinalClass.java

public final class FinalClass {

public final String hello(){
    System.out.println(\"Final class says Hello!!!\");
    return \"0\";
}

}

Foo.java

public class Foo {

public String executeFinal(FinalClass finalClass){
    return finalClass.hello();
}

}

FooTest.java

public class FooTest {

@Test
public void testFinalClass(){
    // Instantiate the class under test.
    Foo foo = new Foo();

    // Instantiate the external dependency
    FinalClass realFinalClass = new FinalClass();

    // Create mock object for the final class. 
    FinalClass mockedFinalClass = mock(FinalClass.class);

    // Provide stub for mocked object.
    when(mockedFinalClass.hello()).thenReturn(\"1\");

    // assert
    assertEquals(\"0\", foo.executeFinal(realFinalClass));
    assertEquals(\"1\", foo.executeFinal(mockedFinalClass));

}

}

Hope it helps.

Complete article present here mocking-the-unmockable.



回答14:

Please look at JMockit. It has extensive documentation with a lot of examples. Here you have an example solution of your problem (to simplify I\'ve added constructor to Seasons to inject mocked RainOnTrees instance):

package jmockitexample;

import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMockit.class)
public class SeasonsTest {

    @Test
    public void shouldStartRain(@Mocked final RainOnTrees rain) {
        Seasons seasons = new Seasons(rain);

        seasons.findSeasonAndRain();

        new Verifications() {{
            rain.startRain();
        }};
    }

    public final class RainOnTrees {
        public void startRain() {
            // some code here
        }

    }

    public class Seasons {

        private final RainOnTrees rain;

        public Seasons(RainOnTrees rain) {
            this.rain = rain;
        }

        public void findSeasonAndRain() {
            rain.startRain();
        }

    }
}


回答15:

Solutions provided by RC and Luigi R. Viggiano together is possibly the best idea.

Although Mockito cannot, by design, mock final classes, the delegation approach is possible. This has its advantages:

  1. You are not forced to change your class to non-final if that is what your API intends in the first place (final classes have their benefits).
  2. You are testing the possibility of a decoration around your API.

In your test case, you deliberately forward the calls to the system under test. Hence, by design, your decoration does not do anything.

Hence you test can also demonstrate that the user can only decorate the API instead of extending it.

On a more subjective note: I prefer keeping the frameworks to a minimum, which is why JUnit and Mockito are usually sufficient for me. In fact, restricting this way sometimes forces me to refactor for good as well.



回答16:

Time saver for people who are facing the same issue (Mockito + Final Class) on Android + Kotlin. As in Kotlin classes are final by default. I found a solution in one of Google Android samples with Architecture component. Solution picked from here : https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample

Create following annotations :

/**
 * This annotation allows us to open some classes for mocking purposes while they are final in
 * release builds.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass

/**
 * Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
 */
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting

Modify your gradle file. Take example from here : https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle

apply plugin: \'kotlin-allopen\'

allOpen {
    // allows mocking for classes w/o directly opening them for release builds
    annotation \'com.android.example.github.testing.OpenClass\'
}

Now you can annotate any class to make it open for testing :

@OpenForTesting
class RepoRepository 


回答17:

As others have stated, this won\'t work out of the box with Mockito. I would suggest using reflection to set the specific fields on the object that is being used by the code under test. If you find yourself doing this a lot, you can wrap this functionality in a library.

As an aside, if you are the one marking classes final, stop doing that. I ran across this question because I am working with an API where everything was marked final to prevent my legitimate need for extension (mocking), and I wish that the developer had not assumed that I would never need to extend the class.



回答18:

If you trying to run unit-test under the test folder, the top solution is fine. Just follow it adding an extension.

But if you want to run it with android related class like context or activity which is under androidtest folder, the answer is for you.



回答19:

Didn\'t try final, but for private, using reflection remove the modifier worked ! have checked further, it doesn\'t work for final.