Подтвердить что ты не робот

Как выполнить модульное тестирование Retrofit API-звонков?

Я пытаюсь интегрировать юнит-тесты для каждого возможного куска кода. Но я сталкиваюсь с проблемами при добавлении тестовых случаев для вызовов API, которые сделаны через модернизацию.

Компилятор JUnit никогда не выполняет код в функциях CallBack.

Есть еще один вариант сделать все вызовы API синхронными для тестирования, но это не возможно для каждого случая в моем приложении.

Как я могу разобраться в этом? Я должен добавить тестовые случаи в вызовах API любым способом.

4b9b3361

Ответ 1

Я тестирую свои обратные вызовы Retrofit с использованием библиотек Mockito, Robolectric и Hamcrest.

Прежде всего, настройте стек lib в вашем модуле build.gradle:

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

В глобальном проекте проекта build.gradle добавьте следующую строку в зависимости buildscript:

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

Затем войдите в меню "Build Variants" в Android Studio (чтобы быстро найти его, нажмите Ctrl + Shift + A и найдите его) и переключите опцию "Test Artifact" в "Unit Tests". Android-студия переключит вашу тестовую папку на "com.your.package(test)" (вместо androidTest).

Хорошо. Настройка завершена, время написать несколько тестов!

Допустим, у вас есть некоторые вызовы API для модификации, чтобы получить список объектов, которые необходимо поместить в какой-либо адаптер для RecyclerView и т.д. Мы хотели бы проверить, заполняется ли адаптер соответствующими элементами при успешном вызове. Чтобы сделать это, нам нужно переключить реализацию интерфейса Retrofit, которую вы используете, чтобы совершать вызовы с имитацией, и делать некоторые ложные ответы, используя преимущества класса 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));
    }
}

Продолжите тестирование, щелкнув правой кнопкой мыши класс теста и нажав "Выполнить".

И это оно. Я настоятельно рекомендую использовать Robolectric (с плагином robolectric gradle) и Mockito, эти библиотеки значительно упростят тестирование приложений для Android. Я изучил этот метод из следующего сообщения в блоге. Также обратитесь к этому ответу.

Обновление: если вы используете Retrofit с RxJava, посмотрите мой другой ответ на этот вопрос.

Ответ 2

Если вы используете .execute() вместо .enqueue(), он выполняет синхронизацию выполнения, поэтому тесты могут выполняться правильно, без необходимости импорта 3 разных библиотек и добавления какого-либо кода или изменения сборки варианты.

Как

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

    }

}

Ответ 3

  • Каркас JUnit никогда не выполняет код в функциях CallBack, потому что основной поток выполнения завершается до получения ответа. Вы можете использовать CountDownLatch как показано ниже:

    @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();
        } 
    }
    
  • Этот тестовый образец также может быть полезен.

  • Мой совет не проводить тестирование ответов API в приложении для Android. Для этого есть много внешних инструментов.

Ответ 4

если уже инкапсуляция retrofit2.0 с rx с restful

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

и запрос API сервера как

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

Ваш сервис перезвонит только один запрос синхронизации Наблюдаемый

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