Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mockito mock static function does not work if the function is called in a Thread

android app, a java class needs to do something based on the NotificationManager's status.

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;
    }
    
}

would like to stub the NotificationManagerCompat.areNotificationsEnabled() to force a test with the return of either true or false.

test using 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(......);
        }
}

but the stub is not called when they are in the Thread. same if stub the currentNotificationsPermission().

Hot to make the stub for a static function to work in the the thread?

like image 799
lannyf Avatar asked Nov 15 '25 10:11

lannyf


1 Answers

There seem to be a few things wrong with your test:

  • Don't mock static methods. Refactor your code to use an interface and an implementation. This will make dependency injection easier too.
  • Read the Javadoc for MockedStatic. It explicitly tells you that the mock works only on the original thread.
  • How does your code compile? utilMoc is a MockedStatic<Util> so how can you call setupByPermission on it?
  • Given that you've mocked Util, calling methods on Util will not call the 'real' method anyway, unless you tell it to. See the example below.
  • Unit testing multithreaded code is difficult. Have a component which is responsible for executing Runnable. In your tests use a dummy implementation which runs the Runnable on the current thread.
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");
        }
    }
}

While people have put in a lot of work to make it possible to mock static methods, as you're seeing it still isn't straightforward. The refactoring needed to avoid it is straightforward and has other benefits.

like image 97
tgdavies Avatar answered Nov 17 '25 07:11

tgdavies



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!