【问题标题】:mockito mock static function does not work if the function is called in a Thread如果在线程中调用函数,则 mockito 模拟静态函数不起作用
【发布时间】:2021-07-12 00:05:26
【问题描述】:

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() 强制测试返回 true 或 false。

使用 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(),则相同。

如何使静态函数的存根在线程中工作?

【问题讨论】:

  • 你为什么嘲笑Util?这不是你正在测试的课程吗?
  • 里面和exec路径中还有更多函数,有待验证(这里sn-p中不包含)。
  • 模拟静态方法是一种主要的气味。

标签: java multithreading unit-testing static mockito


【解决方案1】:

您的测试似乎有一些问题:

  • 不要模拟静态方法。重构代码以使用接口和实现。这也将使依赖注入更容易。
  • 阅读MockedStatic 的Javadoc。它明确告诉您模拟仅适用于原始线程。
  • 您的代码如何编译? utilMocMockedStatic&lt;Util&gt; 那么你怎么能调用 setupByPermission 呢?
  • 鉴于您已经模拟了Util,调用Util 上的方法无论如何都不会调用“真实”方法,除非您告诉它这样做。请参阅下面的示例。
  • 对多线程代码进行单元测试很困难。有一个组件负责执行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");
        }
    }
}

虽然人们付出了很多努力来可能模拟静态方法,但正如您所看到的,它仍然并不简单。避免这种情况所需的重构很简单,而且还有其他好处。

【讨论】:

  • 你也可以通过写foo = Mockito.mockStatic(Foo.class, Mockito.CALLS_REAL_METHODS)) { ...来设置调用真实方法的默认行为
猜你喜欢
  • 2014-06-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-24
相关资源
最近更新 更多