webentwicklung-frage-antwort-db.com.de

Wie kann man Retrofit-API-Aufrufe als Unit-Test durchführen?

Ich versuche, Unit-Testfälle für jeden möglichen Codeabschnitt zu integrieren ..__ Ich habe jedoch Probleme beim Hinzufügen von Testfällen für API-Aufrufe, die durch Nachrüstung erstellt werden.

Der JUnit-Compiler führt niemals den Code in den CallBack-Funktionen aus.

Es gibt eine andere Möglichkeit, alle api-Aufrufe Synchronous zu Testzwecken durchzuführen, aber das ist nicht in jedem Fall in meiner App möglich.

Wie kann ich das klären? Ich muss auf jeden Fall Testfälle in den API-Aufrufen hinzufügen.

48
AabidMulani

Ich teste meine Retrofit-Callbacks mit den Bibliotheken Mockito, Robolectric und Hamcrest.

Richten Sie zunächst den lib-Stack im build.gradle Ihres Moduls ein:

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

Fügen Sie in globalem build.gradle des Jour-Projekts die folgende Zeile zu buildscript-Abhängigkeiten hinzu:

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

Rufen Sie dann das Menü "Build Variants" in Android Studio auf (um es schnell zu finden, drücken Sie Strg + Umschalt + A und suchen Sie danach) und schalten Sie die Option "Artefakt testen" auf "Komponententests". Android Studio wechselt Ihren Testordner zu "com.your.package (test)" (anstelle von androidTest).

OK. Das Setup ist fertig, Zeit, einige Tests zu schreiben!

Nehmen wir an, Sie haben einige Retrofit-API-Aufrufe, um eine Liste der Objekte abzurufen, die in einen Adapter für eine RecyclerView usw. eingefügt werden müssen. Wir möchten testen, ob der Adapter bei einem erfolgreichen Aufruf mit den richtigen Elementen gefüllt wird Dazu müssen wir Ihre Retrofit-Schnittstellenimplementierung ändern, die Sie verwenden, um mit einem Mock Anrufe zu tätigen, und einige falsche Antworten unter Verwendung der Mockito ArgumentCaptor-Klasse durchführen.

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

Fahren Sie mit dem Test fort, indem Sie mit der rechten Maustaste auf die Testklasse klicken und auf Ausführen klicken.

Und das ist es. Ich empfehle dringend die Verwendung von Robolectric (mit robolectric Gradle Plugin) und Mockito. Diese Libs machen das Testen von Android-Apps um einiges einfacher. Ich habe diese Methode aus dem folgenden blog post gelernt. Siehe auch diese Antwort .

Update: Wenn Sie Retrofit mit RxJava verwenden, überprüfen Sie meine andere Antwort dazu auch.

21
maciekjanusz

Wenn Sie .execute () anstelle von .enqueue () verwenden, wird die Ausführung synchron ausgeführt, sodass die Tests ordnungsgemäß ausgeführt werden können, ohne dass 3 verschiedene Bibliotheken importiert und Code hinzugefügt oder die Build-Varianten geändert werden müssen.

Mögen:

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
  • Das JUnit-Framework führt niemals den Code in den CallBack-Funktionen aus, da der Haupt-Thread der Ausführung beendet wird, bevor die Antwort abgerufen wird. Sie können CountDownLatch wie folgt verwenden:

    @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();
        } 
    }
    
  • Dieses Testbeispiel kann auch hilfreich sein.

  • Mein Rat ist nicht, die API-Antworten in der Android-App zu testen. Dafür gibt es viele externe Werkzeuge .
1
Islam Salah

wenn bereits kapseln retrofit2.0 mit rx mit restful

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

und Server-API-Anfrage wie

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

ihr Service rufen Sie zurück, nur eine Synchronisierungsanfrage 

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