Тестирование сети и использование силы Mock
В предыдущей статье мы написали почти полностью готовый функционал по юнит тестированию функций, которые работают с сетью. В этой мы быстро затронем как можно "замокать" некоторые функции в репозитории.
Некоторый функционал нам нет необходимости тестировать или невозможно его имплементировать. Замокав его мы договариваемся, что результат выполнения этого функционала нам всегда известен или же в рамках данного теста его проверять нет необходимости.
Мы добавим еще один Source
для чтения данных локально, например, из базы данных. Но его реализацию писать не будем, этого нам не требуется в рамках задачи по пониманию как "замокать" функционал.
interface LocalSource {
fun getLocalDisneyCharacters(): List<CartoonCharacter>
}
Этот класс по работе с локальными данными мы добавим в наш репозиторий
internal class CharacterRepositoryImpl(
private val remoteSource: RemoteSource,
private val localSource: LocalSource,
) : CharacterRepository {
override fun getRemoteCharacters(): List<CartoonCharacter> {
return remoteSource.getDisneyCharacters()
}
override fun getLocalCharacters(): List<CartoonCharacter> {
return localSource.getLocalDisneyCharacters()
}
}
В общем случае есть два способа как замокать функционал. Первый - это замокать вообще весь класс с использованием Mockito.mock()
. Второй способ чуть более сложный, тесты с ним надо писать аккуратно, чтобы не совершить никаких ошибок (то есть условие Assert.assertEquals
должно всегда выполняться)
Изменим настройку нашего теста в методе setup
@Before
fun setup() {
characterRepository = Mockito.mock(CharacterRepository::class.java) }
Мы сообщили Mockito
что сами будем определять что будет возвращаться в разных функциях, вот, например, как ниже
@Test
fun makeMockWholeClass(){
val mockResult = listOf(CartoonCharacter(id = "5", name = "Donald Duck", universe = "Mock"))
`when`(characterRepository.getLocalCharacters()).thenReturn(mockResult)
val list = characterRepository.getLocalCharacters()
val id1 = list.firstOrNull()?.id
Assert.assertEquals("5", id1)
}
С помощью Mockito.when
мы говорим что должно вернуться при вызове функции getLocalCharacters
.
Снова меняем функцию настройки setup
. Нам нужен функционал для имитации запросов в сеть, а также мы мокаем класс LocalSource. На самом деле здесь это необязательно, можно было бы написать val localSource = object : LocalSource...
потому что в примере это практически не используется.
@Before
fun setup() {
mockWebServer = createMockServer { path, method ->
when (path) {
"/characters" -> "response_characters"
"/episodes" -> "response_episodes"
else -> null
}
}
val api = createApi<ApiTest>(mockWebServer)
val remoteSource = OkRemoteSourceImpl(api)
val localSource = Mockito.mock(LocalSource::class.java)
characterRepository = CharacterRepositoryImpl(remoteSource, localSource)
}
Я хочу в методе makeMockCallWithSpy
протестировать как замоканный функционал, так и обработку ответа с сервера.
@Test
fun makeMockCallWithSpy() {
val spyRepository = Mockito.spy(characterRepository)
val mockResult = listOf(CartoonCharacter(id = "10", name = "Goofy", universe = "Mock"))
`when`(spyRepository.getLocalCharacters()).thenReturn(mockResult)
val list = spyRepository.getLocalCharacters()
val id1 = list.firstOrNull()?.id
Assert.assertEquals("10", id1)
val remoteList = spyRepository.getRemoteCharacters()
val id2 = remoteList.firstOrNull()?.id
Assert.assertEquals("1", id2)
}
Мы указываем, что объект spyRepository
следит за реальным объектом characterRepository
и может некоторые функции переопределять. Тут надо обратить внимание на то, что если мы указали, что Mockito
будет следить за каким-то классом, то все действия и проверки мы будем делать уже над объектом слежения.
В нашем случае им является spyRepository
. Мы говорим, что когда у него будет вызываться метод getLocalCharacters
, требуется вернуть определенный список объектов. Далее мы вызываем обе функции - замоканную и нет - и проверяем результаты.