Как я могу модульно протестировать намерение, запущенное/отправленное из действия?

Как я могу создать тестовый пример Android JUnit, который проверяет содержимое Intent, сгенерированного в Activity?

У меня есть Activity, которое содержит окно EditText, и когда пользователь закончил вводить необходимые данные, Activity запускает Intent для IntentService, который записывает данные и продолжает процесс приложения. Вот класс, который я хочу протестировать, OnEditorActionListener/PasscodeEditorListener создается как отдельный класс:

public class PasscodeActivity extends BaseActivity {
    EditText                    m_textEntry = null;
    PasscodeEditorListener      m_passcodeEditorListener = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.passcode_activity);

        m_passcodeEditorListener = new PasscodeEditorListener();
        m_textEntry = (EditText) findViewById(R.id.passcode_activity_edit_text);
        m_textEntry.setTag(this);
        m_textEntry.setOnEditorActionListener(m_passcodeEditorListener);
    }

    @Override
    protected void onPause() {
        super.onPause();
        /*
         *   If we're covered for any reason during the passcode entry,
         *   exit the activity AND the application...
         */
        Intent finishApp = new Intent(this, CoreService.class);
        finishApp.setAction(AppConstants.INTENT_ACTION_ACTIVITY_REQUESTS_SERVICE_STOP);
        startService(finishApp);
        finish();
    }

}



class PasscodeEditorListener implements OnEditorActionListener{
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        PasscodeActivity activity = (PasscodeActivity) v.getTag();
        boolean imeSaysGo = ((actionId & EditorInfo.IME_ACTION_DONE)!=0)?true:false;
        boolean keycodeSaysGo = ((null != event) && 
                (KeyEvent.ACTION_DOWN == event.getAction()) && 
                (event.getKeyCode() == KeyEvent.KEYCODE_ENTER))?true:false;

        if (imeSaysGo || keycodeSaysGo){
            CharSequence seq = v.getText();
            Intent guidEntry = new Intent(activity, CoreService.class);
            guidEntry.setAction(AppConstants.INTENT_ACTION_PASSCODE_INPUT);
            guidEntry.putExtra(AppConstants.EXTRA_KEY_GUID, seq.toString());
            activity.startService(guidEntry);
            return true;
        }
        return false;
    }
}

Как я могу перехватить два возможных исходящих намерения, созданных действием, и проверить их содержимое?

Спасибо


person Dan Devine    schedule 27.04.2012    source источник
comment
Вы используете симулятор? Может я что-то упускаю, но нельзя ли просто так проверить?   -  person Nick    schedule 27.04.2012
comment
Я использовал и симулятор, и телефон, хотя не думаю, что должна быть разница. Я видел несколько способов внедрить Intent в любую конкретную тестируемую активность, но не так много способов просмотра вывода. Видел еще один пост, в котором они установили ContextWrapper и перехватили вызов startService(). Это работает для первого вызова, но не для последующих вызовов. Активность может запускать несколько намерений без закрытия, мне интересно посмотреть/протестировать их все.   -  person Dan Devine    schedule 27.04.2012


Ответы (2)


Я понял, как использовать ContextWrapper с помощью другого веб-сайта.

Используйте ContextWrapper и переопределите все функции намерения. Обобщая все мои тесты Activity, я расширил класс ActivityUnitTestCase и реализовал решение в виде прокладки. Наслаждаться:

import android.app.Activity;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.test.ActivityUnitTestCase;

public class IntentCatchingActivityUnitTestCase<T extends Activity> extends ActivityUnitTestCase<T> {

    protected Activity m_activity;
    protected Instrumentation m_inst;
    protected Intent[] m_caughtIntents;
    protected IntentCatchingContext m_contextWrapper;

    protected class IntentCatchingContext extends ContextWrapper {
        public IntentCatchingContext(Context base) {
            super(base);
        }

        @Override
        public ComponentName startService(Intent service) {
            m_caughtIntents = new Intent[] { service };
            return service.getComponent();
        }

        @Override
        public void startActivities(Intent[] intents) {
            m_caughtIntents = intents;
            super.startActivities(intents);
        }

        @Override
        public void startActivity(Intent intent) {
            m_caughtIntents = new Intent[] { intent };
            super.startActivity(intent);
        }

        @Override
        public boolean stopService(Intent intent) {
            m_caughtIntents = new Intent[] { intent };
            return super.stopService(intent);
        }
    }

    // --//
    public IntentCatchingActivityUnitTestCase(Class<T> activityClass) {
        super(activityClass);
    }

    protected void setUp() throws Exception {
        super.setUp();
        m_contextWrapper = new IntentCatchingContext(getInstrumentation().getTargetContext());
        setActivityContext(m_contextWrapper);
        startActivity(new Intent(), null, null);
        m_inst = getInstrumentation();
        m_activity = getActivity();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

}
person Dan Devine    schedule 27.04.2012
comment
Это хорошее решение, к сожалению, оно работает только с ActivityUnitTestCase, но не с функциональными тестами. - person Thomas Keller; 27.06.2013
comment
Ага, я согласен. Я также обнаружил, что перехват нескольких намерений также невозможен, например, моя активность может отправить намерение при каком-то условии запуска в IntentService, а затем запустить другое, когда пользователь нажимает кнопку. Всех не успеть. - person Dan Devine; 01.07.2013

В качестве альтернативы вы можете реорганизовать свой код, чтобы выполнить «чистый» модульный тест (я имею в виду модульный тест, в котором высмеивается все, кроме тестируемого класса). На самом деле, у меня самого есть ситуация, когда я получаю java.lang.RuntimeException: Stub!, потому что код, который я хочу протестировать, создает новые намерения, содержащие моки, которые я внедрил.

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

public class MyClassToBeTested {
    public MyClassToBeTested(IntentFactory intentFactory) {
        //assign intentFactory to field
    }
    ....
    public void myMethodToTestUsingIntents() {
        Intent i = intentFactory.create();
        i.setAction(AppConstants.INTENT_ACTION_PASSCODE_INPUT);
        //when doing unit test, inject a mocked version of the
        //IntentFactory and do the necessary verification afterwards.
        ....
    }
}

Моя ситуация отличается от вашей, но я считаю, что вы также можете применить фабричный шаблон для ее решения. Я предпочитаю писать код для поддержки настоящих модульных тестов, но должен признать, что ваше решение довольно умное.

person Vering    schedule 27.08.2013