Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to register a receiver in a test case?

I want to test inside a unit test whether or not an alarm programmed using the AlarmManager is fired, and if so, if it is fired within the correct period.

Here is the receiver class to be tested. I've created it inside my test project. (NOTE: it's not registered in the manifest)

public class MockBroadcastReceiver extends BroadcastReceiver {

    private static int numTimesCalled = 0;

    MockBroadcastReceiver(){
        numTimesCalled = 0;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        numTimesCalled++;           
    }

    public static int getNumTimesCalled() {
        return numTimesCalled;
    }

    public static void setNumTimesCalled(int numTimesCalled) {
        MockBroadcastReceiver.numTimesCalled = numTimesCalled;
    }
}

And here's the unit test. The programReceiver method actually belongs to a class in the main project, but I've included it inside the test so that you don't need to read so much code.

public class ATest extends AndroidTestCase {

    MockBroadcastReceiver mockReceiver;

    @Override
    protected void setUp() throws Exception {
        mockReceiver = new MockBroadcastReceiver();
        getContext().registerReceiver(mockReceiver, new IntentFilter());
    }

    @Override
    protected void tearDown() {     
        getContext().unregisterReceiver(mockReceiver);
        mockReceiver = null;
    }


    public void test(){
        //We're going to program twice and check that only the last
        //programmed alarm should remain active.

        final Object flag = new Object();
        MockBroadcastReceiver.setNumTimesCalled(0);

        new Thread (){
            @Override
            public void run(){
                programReceiver(getContext(), MockBroadcastReceiver.class, 60000, 60000);

                SystemClock.sleep(20000);

                programReceiver(getContext(), MockBroadcastReceiver.class, 60000, 60000);

                SystemClock.sleep(90000);

                synchronized(flag){
                    flag.notifyAll();
                }
            }
        }.start();

        synchronized(flag){
            try {
                flag.wait();
            } catch (InterruptedException e) {
            }
        }

        assertEquals(1, MockBroadcastReceiver.getNumTimesCalled()); //should have been called at least once, but its 0.
    }


    private static void programReceiver(Context context, Class<? extends BroadcastReceiver> receiverClass, long initialDelay, long period){
        Intent intent = new Intent(context, receiverClass);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);     

        alarmManager.cancel(pendingIntent); //Cancel any previous alarm

        alarmManager.setInexactRepeating (
            AlarmManager.RTC_WAKEUP,
            System.currentTimeMillis() + initialDelay,
            period,
            pendingIntent
        );
    }   
}

When I execute the test method, the receiver should have been registered dynamically in the setUp. Then I program the same alarm twice. My intent was to test that only the last alarm remained active, but I'm having trouble getting the receiver called at all. The test fails as it is expected to be called once (or at least a number of times >= 1), but the counter in the mock receiver is 0. I've set a break point in the onReceive method and it is never hit. I've also added logging and nothing shows in the logcat. So I'm 100% sure the receiver is not being called. I've also tried increasing the sleep time in the thread, because setInexactRepeating fires very inexactly, but I can wait for ages and it is still not called.

I've also tried registering it in the test project's manifest instead of programmatically and the results are the same.

Why is the receiver not being called?


UPDATE
I can confirm the AlarmManager is not the issue. Alarms are correctly registered according to adb dumpsys alarm.

I'm now trying to get the receiver to run by calling sendBroadcast, but I'm in a dead end. The receiver just won't get called. I tried the main app context, the test case context, even ActivityInstrumentationTestCase2. Tried also adding WakeLocks and nothing. There's just no way of getting it called. I think this might be caused by some flags in the intent or intent filter (android seems to be really picky with flags).

like image 927
Mister Smith Avatar asked Apr 08 '15 11:04

Mister Smith


1 Answers

In the android source-code there is a alarmManagerTest that executes alarmManager.setInexactRepeating with a broadcastreceiver.

The main difference is that the working android test has a delay of 15 minutes while your test uses 1 minute delay.

The Android documentation for AlarmManager says:

 public void setInexactRepeating (int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)

... intervalMillis interval in milliseconds between subsequent repeats of the alarm. Prior to API 19, if this is one of INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY then the alarm will be phase-aligned with other alarms to reduce the number of wakeups. Otherwise, the alarm will be set as though the application had called setRepeating(int, long, long, PendingIntent). As of API 19, all repeating alarms will be inexact and subject to batching with other alarms regardless of their stated repeat interval.

I am not shure if this means that only multibles of 15 minutes are allowed.

On my Android 2.2 handset the inexact timer only works if it is a multible of 15 minutes.

like image 99
k3b Avatar answered Oct 18 '22 02:10

k3b