web-dev-qa-db-fra.com

Comment tester à l'unité les appels d'api de rattrapage?

J'essaie d'intégrer des scénarios de test unitaires pour chaque bloc de code possible . Mais je suis confronté à des problèmes lors de l'ajout de scénarios de test pour les appels d'API qui sont effectués via la conversion ultérieure.

Le compilateur JUnit n'exécute jamais le code dans les fonctions CallBack} _.

Il existe une autre possibilité de passer tous les appels d'API Synchrone à des fins de test, mais ce n'est pas possible dans tous les cas dans mon application.

Comment puis-je résoudre ce problème? Je dois ajouter des cas de test dans les appels API par quelque moyen que ce soit.

48
AabidMulani

Je teste mes rappels Retrofit en utilisant les bibliothèques Mockito, Robolectric et Hamcrest.

Tout d'abord, configurez la pile de lib dans le build.gradle de votre module:

dependencies {
    testCompile 'org.robolectric:robolectric:3.0'
    testCompile "org.mockito:mockito-core:1.10.19"
    androidTestCompile 'org.hamcrest:hamcrest-library:1.1'
}

Dans le build.gradle global du projet actuel, ajoutez la ligne suivante aux dépendances buildscript:

classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'

Puis entrez dans le menu "Variantes de construction" dans Android Studio (pour le trouver rapidement, appuyez sur Ctrl + Maj + A et recherchez-le), puis basculez l'option "Tester l'artefact" sur "Tests unitaires". Android studio basculera votre dossier de test sur "com.votre.package (test)" (au lieu de androidTest).

D'accord. La configuration est terminée, il est temps d'écrire des tests!

Supposons que vous ayez des appels d'api de modification pour récupérer une liste d'objets devant être insérés dans un adaptateur pour RecyclerView, etc. Nous voudrions vérifier si l'adaptateur se remplit avec les éléments appropriés lors d'un appel réussi . Pour cela, nous devrons changer d'implémentation d'interface Retrofit, que vous utiliserez pour passer des appels fantômes, et créer de fausses réponses en exploitant la classe Mockito ArgumentCaptor.

@Config(constants = BuildConfig.class, sdk = 21,
    manifest = "app/src/main/AndroidManifest.xml")
@RunWith(RobolectricGradleTestRunner.class)
public class RetrofitCallTest {

    private MainActivity mainActivity;

    @Mock
    private RetrofitApi mockRetrofitApiImpl;

    @Captor
    private ArgumentCaptor<Callback<List<YourObject>>> callbackArgumentCaptor;

    @Before
    public void setUp() {            
        MockitoAnnotations.initMocks(this);

        ActivityController<MainActivity> controller = Robolectric.buildActivity(MainActivity.class);
        mainActivity = controller.get();

        // Then we need to swap the retrofit api impl. with a mock one
        // I usually store my Retrofit api impl as a static singleton in class RestClient, hence:
        RestClient.setApi(mockRetrofitApiImpl);

        controller.create();
    }

    @Test
    public void shouldFillAdapter() throws Exception {
        Mockito.verify(mockRetrofitApiImpl)
            .getYourObject(callbackArgumentCaptor.capture());

        int objectsQuantity = 10;
        List<YourObject> list = new ArrayList<YourObject>();
        for(int i = 0; i < objectsQuantity; ++i) {
            list.add(new YourObject());
        }

        callbackArgumentCaptor.getValue().success(list, null);

        YourAdapter yourAdapter = mainActivity.getAdapter(); // Obtain adapter
        // Simple test check if adapter has as many items as put into response
        assertThat(yourAdapter.getItemCount(), equalTo(objectsQuantity));
    }
}

Procédez au test en cliquant avec le bouton droit de la souris sur la classe de test et en appuyant sur Exécuter.

Et c'est tout. Je suggère fortement d'utiliser Robolectric (avec le plugin robolectric gradle) et Mockito, ces bibliothèques facilitent grandement le test des applications Android . J'ai appris cette méthode à partir du blog post . Voir aussi cette réponse .

Update: Si vous utilisez Retrofit avec RxJava, consultez mon autre réponse à ce sujet aussi.

21
maciekjanusz

Si vous utilisez .execute () au lieu de .enqueue (), l'exécution est synchronisée. Les tests peuvent donc s'exécuter correctement sans qu'il soit nécessaire d'importer 3 bibliothèques différentes et d'ajouter du code ou de modifier les variantes de construction.

Comme:

public class LoginAPITest {

    @Test
    public void login_Success() {

        APIEndpoints apiEndpoints = RetrofitHelper.getTesterInstance().create(APIEndpoints.class);

        Call<AuthResponse> call = apiEndpoints.postLogin();

        try {
            //Magic is here at .execute() instead of .enqueue()
            Response<AuthResponse> response = call.execute();
            AuthResponse authResponse = response.body();

            assertTrue(response.isSuccessful() && authResponse.getBearer().startsWith("TestBearer"));

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
13
Adam Varhegyi
  • Le framework JUnit n'exécute jamais le code dans les fonctions CallBack car le thread d'exécution principal se termine avant l'extraction de la réponse. Vous pouvez utiliser CountDownLatch comme indiqué ci-dessous:

    @Test
    public void testApiResponse() {
        CountDownLatch latch = new CountDownLatch(1);
        mApiHelper.loadDataFromBackend(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                System.out.println("Success");
                latch.countDown();
            }
    
            @Override
            public void onFailure(Call call, Throwable t) {
                System.out.println("Failure");
                latch.countDown();
            }
        });
    
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } 
    }
    
  • Cet échantillon de test } peut également être utile.

  • Mon conseil est de ne pas tester les réponses de l'API dans l'application Android. Il existe de nombreux outils externes pour cela.
1
Islam Salah

si déjà encapsulation retrofit2.0 avec rx avec reposant

open class BaseEntity<E> : Serializable {
    /*result code*/
    var status: Int = 0
    /**data */
    var content: E? = null
}

et demande d'api de serveur comme

@GET(api/url)
fun getData():Observable<BaseEntity<Bean>>

votre service rappelle une seule demande de synchronisation Observable 

val it = service.getData().blockingSingle()
assertTrue(it.status == SUCCESS_CODE)
0
shuabing