参照 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 在子线程执行的时候,如果使用 Powermock 的 WhenNew 对被测试对象进行仿真的时候,无法正确的被 Mock。
被测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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(); } } |
1 2 3 4 5 |
public class CustomMocker { public String getMessage() { return "I am not mocked"; } } |
单元测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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
,于是修改测试用例为如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
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()); } } |
结果出现一个诡异的现象,如果在创建对象的地方设置断点进行调试跟踪,就是正确的,去掉断点,就会失败。
这个问题卡住很久,结果突然想起以前写的一个差不多功能的被测试类,是能正常工作的,于是进行了代码对比,结果发现一个奇怪的现象,只要内部定义一个类来中继一下,就可以完美解决问题。于是修改被测试类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
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(); } } } |
也就是内部定义来一个代理子类,初始化的时候,通过内部子类进行初始化。
这个现象比较奇怪,目前能解决问题,具体原因还是不详。