Robolectric 3.8/PowerMock无法Mock由Executors线程运行的类

参照 Android Studio 3.6.3/4.0/4.1/4.2配置Robolectric-3.8/4.3.1/4.5.1/4.6.1 Powermock-1.6.6单元测试环境 进行单元测试,不过,由于工程的限制(不能依赖 AndroidX),我们只能在 Robolectric 3.8

Android Studio 4.1.3,JDK使用 Java 1.8

在执行如下测试用例的时候,发现当被测试代码使用 Executors 在子线程执行的时候,如果使用 PowermockWhenNew 对被测试对象进行仿真的时候,无法正确的被 Mock

被测试代码如下:

import android.support.annotation.NonNull;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CustomExecutor {
    private final static String TAG = "CustomExecutor";
    private static volatile CustomExecutor instance;
    private CustomMocker custom;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private CustomExecutor() {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                custom = new CustomMocker();
            }
        });
    }

    @NonNull
    public static CustomExecutor getInstance() {
        if (null == instance) {
            synchronized (CustomExecutor.class) {
                if (null == instance) {
                    instance = new CustomExecutor();
                }
            }
        }
        return instance;
    }

    public String getMessage() throws Exception {
        final Future<String> future = executor.submit(new Callable<String>() {
            @Override
            public String call() {
                return custom.getMessage();
            }
        });
        return future.get();
    }
}
public class CustomMocker {
    public String getMessage() {
        return "I am not mocked";
    }
}

单元测试代码如下:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;


@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({CustomMocker.class, CustomExecutor.class})
@Config(manifest = Config.NONE, constants = BuildConfig.class, sdk = 26)
public class CustomExecutorTest {
    @Rule
    public final PowerMockRule rule = new PowerMockRule();

    @Before
    public void setup() throws Exception {
        final CustomMocker clz = PowerMockito.mock(CustomMocker.class);
        PowerMockito.when(clz.getMessage()).thenReturn("I am mocked!");
        PowerMockito.whenNew(CustomMocker.class).withAnyArguments().thenReturn(clz);
    }

    @Test
    public void testCustomExecutors() {
        final CustomExecutor executor = CustomExecutor.getInstance();
        Assert.assertNotNull(executor);
        Assert.assertEquals(expStr, executor.getMessage());
    }
}

上述的测试代码在执行的时候,完全不会生效。

网上查询了一下,根据 Unable to get mocked instance of Executor in separate class 的介绍,我们需要在测试用例里面的`@PrepareForTest`里面增加 `Executors.class`,于是修改测试用例为如下:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.junit4.PowerMockRunnerDelegate;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(RobolectricTestRunner.class)
@PowerMockIgnore({"jdk.internal.reflect.*", "org.apache.commons.*", "org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({ExecutorService.class, Executors.class, CustomMocker.class, CustomExecutor.class})
@Config(sdk = 26)
public class CustomExecutorTest {
    private final static String expStr = "I am mocked!";
    @Rule
    public final PowerMockRule rule = new PowerMockRule();

    @Before
    public void setup() throws Exception {
        PowerMockito.mockStatic(Executors.class);
        PowerMockito.mockStatic(ExecutorService.class);
        PowerMockito.when(Executors.newSingleThreadExecutor()).thenCallRealMethod();

        PowerMockito.mockStatic(CustomExecutor.class);
        final CustomMocker clz = PowerMockito.mock(CustomMocker.class);
        PowerMockito.when(clz.getMessage()).thenReturn(expStr);
        PowerMockito.whenNew(CustomMocker.class).withAnyArguments().thenReturn(clz);

        PowerMockito.mockStatic(CustomExecutor.class);
        PowerMockito.when(CustomExecutor.getInstance()).thenCallRealMethod();
    }

    @Test
    public void testExecutorsMock() throws Exception {
        final CustomExecutor executor = CustomExecutor.getInstance();
        Assert.assertNotNull(executor);
        Assert.assertEquals(expStr, executor.getMessage());
    }
}

结果出现一个诡异的现象,如果在创建对象的地方设置断点进行调试跟踪,就是正确的,去掉断点,就会失败。

这个问题卡住很久,结果突然想起以前写的一个差不多功能的被测试类,是能正常工作的,于是进行了代码对比,结果发现一个奇怪的现象,只要内部定义一个类来中继一下,就可以完美解决问题。于是修改被测试类,代码如下:

import android.support.annotation.NonNull;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CustomExecutor {
    private static volatile CustomExecutor instance;
    @NonNull
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private Delegate delegate;

    private CustomExecutor() {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                delegate = new Delegate();
            }
        });
    }

    @NonNull
    public static CustomExecutor getInstance() {
        if (null == instance) {
            synchronized (CustomExecutor.class) {
                if (null == instance) {
                    instance = new CustomExecutor();
                }
            }
        }
        return instance;
    }

    public String getMessage() throws Exception {
        final Future<String> future = executor.submit(new Callable<String>() {
            @Override
            public String call() {
                return delegate.getMessage();
            }
        });
        return future.get();
    }

    private static class Delegate {
        private final CustomMocker mocker;

        public Delegate() {
            mocker = new CustomMocker();
        }

        public String getMessage() {
            return mocker.getMessage();
        }
    }
}

也就是内部定义来一个代理子类,初始化的时候,通过内部子类进行初始化。

这个现象比较奇怪,目前能解决问题,具体原因还是不详。

参考链接


发布者

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注