java - 如果在线程中调用函数,则 mockito 模拟静态函数不起作用
问题描述
android app,一个java类需要根据NotificationManager的状态做一些事情。
class Util {
static void setupByPermission(@NonNull final Context appContext) {
Thread t = new Thread(new Runnable() {
public void run() {
try {
NotificationManagerCompat nm = NotificationManagerCompat.from(appContext); // should got from stub
boolean overallPermission = currentNotificationsPermission(nm);
if (overallPermission) {
doWithPermission();
} else {
doWithoutPermission();
}
} catch (Throwable ex) {}
}
});
t.start();
}
static boolean currentNotificationsPermission(@NonNull NotificationManagerCompat nm) {
System.out.println("+++ enter currentNotificationsPermission("+nm+")");
boolean overallPermission = nm.areNotificationsEnabled();// should got result from stub
System.out.println("+++ ========= in currentNotificationsPermission("+nm+"), nm.areNotificationsEnabled() ==> "+overallPermission);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (overallPermission) {
List<NotificationChannel> channels = nm.getNotificationChannels();
boolean someChannelEnabled = channels.isEmpty();
for (NotificationChannel channel : channels) {
if (channel.getImportance() != NotificationManagerCompat.IMPORTANCE_NONE) {
someChannelEnabled = true;
break;
}
}
overallPermission = overallPermission && someChannelEnabled;
}
}
System.out.println("+++ --- exit =========== currentNotificationsPermission(), overallPermission:"+overallPermission);
return overallPermission;
}
}
想要存根NotificationManagerCompat.areNotificationsEnabled()
以强制测试返回真或假。
使用 mockito-inline 3.8.0 进行测试
@Test
public void test () throws Exception {
try (MockedStatic<NotificationManagerCompat> nmMoc = Mockito.mockStatic(NotificationManagerCompat.class);
MockedStatic<Util> utilMoc = Mockito.mockStatic(Util.class)
) {
NotificationManagerCompat nmSpy = spy(NotificationManagerCompat.from(application));
when(nmSpy.areNotificationsEnabled())
.thenReturn(false); //or true
nmMoc.when(() -> NotificationManagerCompat.from(any(Context.class)))
.thenReturn(nmSpy);
// test
final CountDownLatch latch = new CountDownLatch(1);
utilMoc.setupByPermission(application);
latch.await(2, TimeUnit.SECONDS);
Mockito.verify(......);
}
}
但是当它们在线程中时,不会调用存根。如果存根currentNotificationsPermission()
.
使静态函数的存根在线程中工作很热?
解决方案
您的测试似乎有一些问题:
- 不要模拟静态方法。重构代码以使用接口和实现。这也将使依赖注入更容易。
- 阅读 Javadoc 以获取
MockedStatic
. 它明确告诉您模拟仅适用于原始线程。 - 你的代码是如何编译的?
utilMoc
是一个MockedStatic<Util>
那么你怎么能打电话setupByPermission
呢? - 鉴于您已经 mocked
Util
,调用方法 onUtil
无论如何都不会调用“真实”方法,除非您告诉它。请参见下面的示例。 - 单元测试多线程代码很困难。有一个负责执行的组件
Runnable
。Runnable
在您的测试中使用在当前线程上运行的虚拟实现。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class TestExample {
public static class Foo {
public static String bar() {
return "bar";
}
}
@Test
public void aTest() {
try (MockedStatic<Foo> foo = Mockito.mockStatic(Foo.class)) {
// without this line Foo.bar() will return null
when(Foo.bar()).thenCallRealMethod();
assertThat(Foo.bar()).isEqualTo("bar");
}
}
}
尽管人们已经投入了大量工作以使模拟静态方法成为可能,但正如您所看到的那样,它仍然不是直截了当的。避免这种情况所需的重构很简单,并且还有其他好处。
推荐阅读
- mysql - datadrip 和 phpmyadim 上不显示数据库关系线
- ubuntu-16.04 - 如何阻止 icmpv6 邻居广告风暴
- python - 具有两个以上输入的 Keras 自定义指标
- amazon-web-services - Kubernetes 服务无法在 AWS-EKS 上访问
- php - symfony 更新 2.6 到 3.4 后奏鸣曲/树枝错误
- c# - 不要在 localDataBace 中显示插入平铺第二次插入
- routing - openstreetmap 工具路由+高速公路标签/road_class
- r - 如何在 R Shiny 应用程序中使用工作区对象
- cron - 如何在 Mac OSX Mojave 中授予 cron 作业网络摄像头访问权限?
- google-chrome - Chrome - 停止自动更新