我该如何对从Activity启动/发送的Intent进行单元测试?

7
如何创建一个Android JUnit测试用例,测试在Activity中生成的Intent的内容?
我有一个包含EditText窗口的Activity,在用户完成所需数据的输入后,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;
    }
}

我该如何拦截活动生成的两个可能的出站意图并验证它们的内容?

谢谢


你在使用模拟器吗?也许我错过了什么,但你不能用那种方式测试一下吗? - Nick
我一直在使用模拟器和手机进行测试,虽然我认为它们不应该有什么区别。我看到了很多将Intent注入到任何特定被测试Activity的方法,但是没有太多观察输出的方法。我看到另一篇帖子中他们设置了ContextWrapper并拦截了对“startService()”的调用。这对第一个调用有效,但对于后续调用则无效。一个Activity可以启动多个Intent而不关闭,我对观察/测试它们都很感兴趣。 - Dan Devine
2个回答

6

在另一个网站的帮助下,我学会了如何使用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();
    }

}

这是一个很好的解决方案,但遗憾的是它只适用于 ActivityUnitTestCase,而不适用于功能性测试用例。 - Thomas Keller
是的,我同意。我也发现捕获多个Intent也是不可能的,例如,我的Activity在某些启动条件下可能会向IntentService发送一个Intent,然后在用户按下按钮时启动另一个Intent。无法全部捕获。 - Dan Devine

1

或者,您可以重新设计代码以进行“干净”的单元测试(我指的是除被测试类外的所有内容都被模拟的单元测试)。实际上,我自己也遇到了一个情况,其中我得到了java.lang.RuntimeException: Stub!,因为我想对其进行单元测试的代码创建了包含我注入的模拟对象的新 Intent。

我考虑创建自己的意图工厂。然后,我可以将一个模拟的工厂注入到我的被测试类中:

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

我的情况和你的不同,但是我相信你也可以应用工厂模式来解决它。我更喜欢编写代码来支持真正的单元测试,但必须承认你的解决方案非常聪明。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接