Возможно ли зарегистрировать приемник в тестовом примере?

Я хочу протестировать внутри unit test, был ли запущен сигнал тревоги, запрограммированный с помощью AlarmManager, и если да, то если он запущен в течение правильного периода.

Здесь тестируется класс приемника. Я создал его в своем тестовом проекте. (ПРИМЕЧАНИЕ: он не зарегистрирован в манифесте)

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

И вот unit test. Метод programReceiver фактически принадлежит классу в основном проекте, но я включил его внутри теста, так что вам не нужно читать столько кода.

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

Когда я выполняю метод test, приемник должен быть динамически зарегистрирован в setUp. Затем я программирую один и тот же сигнал тревоги дважды. Мое намерение состояло в том, чтобы проверить, что только последний сигнал остался активным, но мне не удается вообще вызвать приемник. Тест завершается неудачно, поскольку ожидается, что он будет вызываться один раз (или, по крайней мере, несколько раз >= 1), но счетчик в макетном приемнике равен 0. Я установил точку останова в методе onReceive, и это никогда не ударил. Я также добавил регистрацию и ничего не отображается в logcat. Поэтому я на 100% уверен, что приемник не вызван. Я также пытался увеличить время сна в потоке, потому что setInexactRepeating срабатывает очень неточно, но я могу ждать веков, и он все еще не вызван.

Я также попытался зарегистрировать его в манифесте тестового проекта, а не программно, и результаты совпадают.

Почему приемник не вызывается?


UPDATE
Я могу подтвердить AlarmManager не проблема. Тревоги правильно зарегистрированы в соответствии с аварийным сигналом adb dumpsys.

Теперь я пытаюсь запустить приемник, вызвав sendBroadcast, но я в тупике. Приемник просто не будет вызван. Я попробовал контекст основного приложения, контекст тестового случая, даже ActivityInstrumentationTestCase2. Пробовал также добавлять WakeLocks и ничего. Просто нет способа заставить его позвонить. Я думаю, что это может быть вызвано некоторыми флагами в намеренном или намеренном фильтре (андроид, по-видимому, очень разборчив с флагами).

Ответ 1

Итак, ответ да, это возможно, НО приемники будут работать только с неявными (основанными на действии) намерениями и IntentFilters.

Явные намерения (основанные на классе, без фильтров) не работают с динамически зарегистрированными приемниками. Чтобы явные намерения работать, вам необходимо зарегистрировать приемник в манифесте, а не динамически, как я пытался.

Это одна из самых темных и наименее документированных функций Android. По моему опыту с намерениями вы никогда не сможете быть уверенными. И это также очень сложно диагностировать, так как в логарифме не было предупреждений, которые могли бы помочь понять проблему. Kudos to @d2vid ответьте здесь: fooobar.com/questions/536784/...

Итак, чтобы исправить мой код, мне пришлось бы добавить элемент-получатель внутри тега приложения в манифесте. Поскольку приемник - это класс прикомандированного приемника, созданный внутри тестового проекта, мне придется отредактировать манифест тестового проекта, а не основной проект. Но, с другой стороны, в тестовых проектах Eclipse приемные теги, добавленные в манифест, не работают. Android Studio лучше подходит для слияния манифеста, но это устаревший проект, запущенный в Eclipse, и мы его не переносим.

В заключение: явное тестирование намерений в Eclipse нарушается для тех приемников, которые не существуют в основном проекте. Класс, который мне нужен для тестирования, использует только явные намерения, поэтому мой тест не может быть записан в Eclipse.

Ответ 2

В исходном коде Android есть alarmManagerTest, который выполняет alarmManager.setInexactRepeating с транслятором вещания.

Основное различие заключается в том, что рабочий тест для Android имеет задержку в 15 минут, в то время как ваш тест использует задержку в 1 минуту.

Документация для Android для AlarmManager говорит:

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

... intervalMillis интервал в миллисекундах между последующими повторами тревоги. До API 19, если это один из INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY или INTERVAL_DAY, тогда сигнал тревоги будет выровнен по фазе с другими аварийными сигналами, чтобы уменьшить количество пробуждений. В противном случае будильник будет установлен так, как если бы приложение вызывало setRepeating (int, long, long, PendingIntent). Начиная с API 19, все повторяющиеся сигналы тревоги будут неточными и подлежат пакетной обработке с другими аварийными сигналами независимо от их указанного интервала повторения.

Я не уверен, если это означает, что разрешено использовать только многократные 15 минут.

На моем телефоне Android 2.2 неточный таймер работает только в том случае, если он представляет собой комбинацию из 15 минут.

Ответ 3

Я не уверен, почему ваш тест работает не так, как ожидалось. Несколько предложений spring:

  • Большая часть doco, которую я видел, например. [1], предполагает, что Object.wait() всегда следует вызывать в цикле на основе условия, которое не выполняется. Ваш код этого не делает. Возможно, вы можете попробовать переработать его так, чтобы он это делал.
  • Для полноты, возможно, вы должны вывести что-то для InterruptedException, которое может быть выбрано flag.wait() на всякий случай.

[1] https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--