广播接收器单元测试?

43

这是来自我的项目的BroadcastReceiver,我希望对它进行单元测试。当用户拨打电话时,它获取电话号码,并设置一个意图启动一个新活动,将电话号码传递进去。

public class OutgoingCallReceiver extends BroadcastReceiver 
{
    @Override
    public void onReceive(Context xiContext, Intent xiIntent) 
    {
        if (xiIntent.getAction().equalsIgnoreCase(Intent.ACTION_NEW_OUTGOING_CALL))
        {
            String phoneNum = xiIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);

            Intent intent = new Intent(xiContext, MyActivity.class);
            intent.putExtra("phoneNum", phoneNum);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

            xiContext.startActivity(intent);
            setResultData(null);
        }
    }
}

到目前为止,我的单元测试看起来像这样:

public class OutgoingCallReceiverTest extends AndroidTestCase
{
    private OutgoingCallReceiver mReceiver;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();

        mReceiver = new OutgoingCallReceiver();
    }

    public void testStartActivity()
    {
        Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
        intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");

        mReceiver.onReceive(getContext(), intent);        
    }
}

这段代码可以运行,但我希望我的测试能够检查是否已发送意图,并检查其上的电话号码。我该怎么做?

我还可以测试电话呼叫是否被取消(因为有 setResultData(null) 这行代码)吗?

3个回答

50

corlettk指出了Android中的MockContext对象,它可以胜任这项工作。我创建了它的子类TestContext,代码如下:

public class TestContext extends MockContext
{
    private List<Intent> mReceivedIntents = new ArrayList<Intent>();

    @Override
    public String getPackageName()
    {
        return "com.mypackage.test";
    }

    @Override
    public void startActivity(Intent xiIntent)
    {
        mReceivedIntents.add(xiIntent);
    }

    public List<Intent> getReceivedIntents()
    {
        return mReceivedIntents;
    }
}

现在我的测试案例看起来像这样:

public class OutgoingCallReceiverTest extends AndroidTestCase
{
    private OutgoingCallReceiver mReceiver;
    private TestContext mContext;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();

        mReceiver = new OutgoingCallReceiver();
        mContext = new TestContext();
    }

    public void testStartActivity()
    {
        Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
        intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");

        mReceiver.onReceive(mContext, intent);        
        assertEquals(1, mContext.getReceivedIntents().size());
        assertNull(mReceiver.getResultData());

        Intent receivedIntent = mContext.getReceivedIntents().get(0);
        assertNull(receivedIntent.getAction());
        assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum"));
        assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0);
    }
}

不错,马特...那是一个很好的、简洁的测试。 - corlettk
嗯,我注意到上面的代码仍然调用onReceive(getContext()) - 现在不应该是'mContext'吗?打字错误? :) - dimsuz
我正在尝试使用上述代码为我的广播编写单元测试,但是在这里我的接收器中出现了空指针异常:"if (xiIntent.getAction().equalsIgnoreCase(Intent.ACTION_NEW_OUTGOING_CALL))"... 有什么想法吗? - kukroid

28

Matt,

看起来你需要模拟一个上下文...然后将你的方法转换为接受接口而不是具体类:public void onReceive(IContext c, IIntent i),只是为了测试。但是上下文和意图类不是你的,而是Android的...所以你不能“只是”让它们实现你的接口,所以你必须“包装”它们以公开你的接口,这需要写相当多的代码,而收益却很小。非常糟糕!

因此,我开始想知道是否有人已经经历过所有这些,并为我们完成了艰难的工作... 然后tada:http://developer.android.com/reference/android/test/mock/package-summary.html

干杯。Keith。


6

自从提出这个问题以来,模拟框架已经得到了很大的发展。使用mockito,你现在不仅可以模拟接口,还可以模拟类。因此,我建议通过模拟上下文并使用ArgumentCapture来解决这个问题:

import static org.mockito.Mockito.*;

public class OutgoingCallReceiverTest extends AndroidTestCase {
    private OutgoingCallReceiver mReceiver;
    private Context mContext;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        //To make mockito work
        System.setProperty("dexmaker.dexcache", 
                mContext.getCacheDir().toString());

        mReceiver = new OutgoingCallReceiver();
        mContext = mock(Context.class);
    }

    public void testStartActivity() {
        Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
        intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");

        mReceiver.onReceive(mContext, intent);
        assertNull(mReceiver.getResultData());

        ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
        verify(mContext, times(1)).startActivity(argument.capture());

        Intent receivedIntent = argument.getValue();         
        assertNull(receivedIntent.getAction());
        assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum"));
        assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0);
    }
}

在 'mContext = mock(Context.class);' 中,'mock' 是什么意思? - Hamid
mock(Context.class) 使用Mockito来模拟Context类。 - Kitesurfer
1
我尝试了这个,但是出现了“java.lang.RuntimeException:android.content.Intent中的putExtra方法未被模拟”的错误。有什么想法吗? - ExplodingTiger

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