Тестирование Retrofit + Moshi ApiService, который возвращает отложенный объект - PullRequest
0 голосов
/ 06 мая 2020

Я пытался создать тесты для следующей службы API, но не могу понять, как создать для нее фиктивный интерфейс:

package com.example.themovieapp.network

import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import kotlinx.coroutines.Deferred
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query

private const val BASE_URL = "https://api.themoviedb.org/3/"
private const val API_key  = ""

private val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .build()

private val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .baseUrl(BASE_URL)
    .build()


interface MovieApiService{
//https://developers.themoviedb.org/3/movies/get-top-rated-movies
//https://square.github.io/retrofit/2.x/retrofit/index.html?retrofit2/http/Query.html
    @GET("movie/top_rated")
    fun getMoviesAsync(
        @Query("api_key") apiKey: String = API_key,
        @Query("language") language: String = "en-US",
        @Query("page") page: Int
    ): Deferred<ResponseObject>
}


/*
Because this call is expensive, and the app only needs
one Retrofit service instance, you expose the service to the rest of the app using
a public object called MovieApi, and lazily initialize the Retrofit service there
*/
object MovieApi {
    val retrofitService: MovieApiService by lazy {
        retrofit.create(MovieApiService::class.java)
    }
}

Я хочу создать модульные тесты, которые:

Проверить статус HTTPS &

Убедитесь, что ответ JSON подходит.

Если это поможет, это используется в другом файле для создания запроса API:

coroutineScope.launch {
            val getMoviesDeferred = MovieApi.retrofitService.getMoviesAsync(page = pageNumber)
//...
            val responseObject = getMoviesDeferred.await()
//...
}

data class ResponseObject(
    val page: Int,
    val results: List<Movie>,
    val total_results: Int,
    val total_pages: Int
)

1 Ответ

2 голосов
/ 07 мая 2020

Вы можете добиться этого с помощью MockWebServer

class MovieApiTest {
    private var mockWebServer = MockWebServer()

    private lateinit var apiService: MovieApiService

    @Before
    fun setUp() {
        // checkthis blogpost for more details about mock server
        // https://medium.com/@hanru.yeh/unit-test-retrofit-and-mockwebserver-a3e4e81fd2a2
        mockWebServer.start()

        apiService = Retrofit.Builder()
                .addConverterFactory(MoshiConverterFactory.create(moshi))
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .baseUrl(mockWebServer.url("/")) // note the URL is different from production one
                .build()
                .create(MovieApiService::class.java)
    }

    @After
    fun teardown() {
        mockWebServer.shutdown()
    }

    @Test
    fun testCompleteIntegration() = runBlocking { // that will allow to wait for coroutine
        mockWebServer.enqueue(MockResponse()
                .setResponseCode(HttpURLConnection.HTTP_OK)
                .setBody("""{
                    "page":0,
                    "total_results":1,
                    "total_pages":1,
                    "results": [{"id": "movie_id"}]
                }"""))

        val response = apiService.getMoviesAsync(page = 1).await()

        assertEquals(0, response.page)
        assertEquals(1, response.total_results)
        assertEquals(1, response.total_pages)
        assertEquals("movie_id", response.results.first().id)
    }
}

Таким образом вы можете избежать вызова реального сервера, который не будет хорошо работать в модульных тестах из-за задержки и недетерминированности c состояние сети.

Кроме того, я рекомендую разбивать код на части, чтобы вы могли тестировать их независимо, если у вас сложная логика синтаксического анализа. c: сделайте поля необязательными и определите отдельный модуль отображения, который может проверять, какие части из JSON являются обязательными, и которые являются обязательными, и протестируйте этот сопоставитель отдельно.

...