首页 > 解决方案 > 如果在线程中调用函数,则 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().

使静态函数的存根在线程中工作很热?

标签: javamultithreadingunit-testingstaticmockito

解决方案


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

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

尽管人们已经投入了大量工作以使模拟静态方法成为可能,但正如您所看到的那样,它仍然不是直截了当的。避免这种情况所需的重构简单,并且还有其他好处。


推荐阅读