web-dev-qa-db-fra.com

Unité testant un récepteur de diffusion?

Voici un BroadcastReceiver de mon projet, que je cherche à tester unitaire. Lorsque l'utilisateur passe un appel téléphonique, il saisit le numéro de téléphone et définit l'intention de démarrer une nouvelle activité, en transmettant le numéro de téléphone.

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

Jusqu'à présent, mon test unitaire ressemble à ceci:

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

Cela passe par le code, mais je veux que mon test puisse vérifier que l'intention a été envoyée et vérifier le numéro de téléphone. Comment puis-je faire cela?

Puis-je également tester que l'appel téléphonique est annulé (en raison de la ligne setResultData (null))?

41
thomson_matt

Mat,

On dirait que vous devez modéliser un contexte ... puis échanger vos méthodes pour accepter des interfaces au lieu de classes concrètes: public void onReceive(IContext c, IIntent i), uniquement à des fins de test. Mais alors les classes Context et Intent ne sont pas les vôtres, elles sont ... elles sont Android ... donc vous ne pouvez pas "simplement" les faire implémenter vos interfaces, donc vous devriez les "envelopper" pour exposer une interface, ce qui est plutôt beaucoup de code pour pas beaucoup de gain. Très dégoûtant !!!

J'ai donc commencé à me demander si quelqu'un avait déjà traversé tout cela avant, et fait les travaux difficiles pour nous ... et tada: http://developer.Android.com/reference/Android/test/mock/package -summary.html

À votre santé. Keith.

27
corlettk

corlettk m'a pointé sur l'objet MockContext dans Android, qui fait l'affaire. J'en ai fait une sous-classe, TestContext, qui ressemble à ceci:

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

Et mon cas de test ressemble maintenant à ceci:

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

Depuis que cette question a été posée, les cadres railleurs ont évolué à peu près. Avec mockito, vous pouvez désormais vous moquer non seulement des interfaces mais aussi des classes. Je suggère donc de résoudre ce problème en se moquant d'un contexte et en utilisant 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);
    }
}
6
Pascal Zaugg