How to mock a final class with mockito

2019-01-01 15:26发布

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?

19条回答
与君花间醉酒
2楼-- · 2019-01-01 15:58

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.

查看更多
ら面具成の殇う
3楼-- · 2019-01-01 15:58

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 
查看更多
心情的温度
4楼-- · 2019-01-01 15:59

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.

查看更多
梦寄多情
5楼-- · 2019-01-01 16:00

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

查看更多
还给你的自由
6楼-- · 2019-01-01 16:02

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.

查看更多
路过你的时光
7楼-- · 2019-01-01 16:02

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();
        }

    }
}
查看更多
登录 后发表回答