Модульные тесты проходят только индивидуально - PullRequest
0 голосов
/ 05 августа 2020

Тесты проходят, когда я запускаю их по отдельности, но когда я запускаю их все, проходит только первый, а остальные терпят неудачу с нулевыми данными. не работает.

Я публикую как можно больше информации ниже. Не знаю, почему после первого теста liveata обнуляется.

gradle (app)

androidTestImplementation "androidx.test.ext:junit:1.1.1"
    androidTestImplementation "androidx.test:runner:1.2.0"
    androidTestImplementation "androidx.test:rules:1.2.0"
    androidTestImplementation "androidx.test:core:1.2.0"
    androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.espressoVersion"
    androidTestImplementation "androidx.test.espresso:espresso-contrib:$rootProject.espressoVersion"
    implementation "androidx.test.espresso:espresso-idling-resource:$rootProject.espressoVersion"

    //Test Coroutines
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.8"

    //Junit 5
    testImplementation "org.junit.jupiter:junit-jupiter-api:5.5.2"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.5.2"
    androidTestRuntimeOnly "de.mannodermaus.junit5:android-test-runner:1.1.0"

    //Test Rule Architecture Components
    testImplementation "androidx.arch.core:core-testing:$rootProject.archTestVersion"

    // MockK
    testImplementation "io.mockk:mockk:1.9.3"

Tests

import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.io.IOException


@ExtendWith(InstantExecutorExtension::class)
class SearchViewModelTest {
    private val dispatcher = TestCoroutineDispatcher()
    private val tmdbApi = mockk<TmdbApi>()
    private var viewModel: SearchViewModel? = null
    private val movieTitle = "nemo"
    private val successResponse = NetworkResponse.Success(
        MovieResponseDTO(listOf(
            MovieDTO(
                false,
                "backdrop-path",
                arrayListOf("1", "2", "3", "4"),
                arrayListOf("comedy, drama, adventure"),
                1234,
                "en-US",
                "Finding Nemo",
                "movie overview",
                90.0,
                "poster-path",
                "2020",
                "Finding Nemo",
                false,
                10.0,
                200000
            )
        ))
    )

    private val networkErrorResponse = NetworkResponse.NetworkError(IOException())
    private val apiErrorResponse = NetworkResponse.ApiError(Error(500, "unavailable"), 500)
    private val unknownErrorResponse = NetworkResponse.UnknownError(Throwable())

    @BeforeEach
    fun init() {
        viewModel = SearchViewModel(tmdbApi, dispatcher)
        Dispatchers.setMain(dispatcher)
    }

    @AfterEach
    fun tearDown() {
        viewModel = null
        Dispatchers.resetMain()
        dispatcher.cleanupTestCoroutines()
    }

    @Test
    fun `make request, returns 200`() {
        //Arrange
        coEvery { tmdbApi.searchMovieSuspend(AppConstants.LANGUAGE, movieTitle, false) } answers {
            successResponse
        }
        //Act
        viewModel?.searchMovie(movieTitle)

        //Assert
        assertEquals(null, viewModel?.errorMessage?.value)
        assertEquals(false, viewModel?.errorScreenVisibility?.value)
        assertEquals(successResponse.body.results, viewModel?.searchResult?.value)
    }

    @Test
    fun `make request, returns 400`() {
        //Arrange
        coEvery { tmdbApi.searchMovieSuspend(AppConstants.LANGUAGE, movieTitle, false) } answers {
            networkErrorResponse
        }
        //Act
        viewModel?.searchMovie(movieTitle)

        //Assert
        assertEquals(null, viewModel?.searchResult?.value)
        assertEquals(true, viewModel?.errorScreenVisibility?.value)
        assertEquals(AppConstants.NETWORK_ERROR_MESSAGE, viewModel?.errorMessage?.value)
    }

    @Test
    fun `make request, returns 500`() {
        //Arrange
        coEvery { tmdbApi.searchMovieSuspend(AppConstants.LANGUAGE, movieTitle, false) } answers {
            apiErrorResponse
        }
        //Act
        viewModel?.searchMovie(movieTitle)


        //Assert
        assertEquals(null, viewModel?.searchResult?.value)
        assertEquals(true, viewModel?.errorScreenVisibility?.value)
        assertEquals(AppConstants.API_ERROR_MESSAGE, viewModel?.errorMessage?.value)
    }
}

InstantExecutorExtension

import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.ExtensionContext

class InstantExecutorExtension : BeforeAllCallback, AfterEachCallback {
    override fun beforeAll(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
            override fun executeOnDiskIO(runnable: Runnable) {
                runnable.run()
            }

            override fun isMainThread(): Boolean {
                return true
            }

            override fun postToMainThread(runnable: Runnable) {
                runnable.run()
            }

        })
    }

    override fun afterEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(null)
    }
}

ViewModel

class SearchViewModel @Inject constructor(private val tmdbApi: TmdbApi, @MainDispatcher val mainDispatcher: CoroutineDispatcher) : ViewModel() {

    private var _searchResult = MutableLiveData<List<MovieDTO>>()
    var searchResult: LiveData<List<MovieDTO>> = _searchResult

    private var _searchProgressBarVisibility = MutableLiveData<Boolean>(false)
    var searchProgressBarVisibility: LiveData<Boolean> = _searchProgressBarVisibility

    private var _errorScreenVisibility = MutableLiveData<Boolean>(false)
    var errorScreenVisibility: LiveData<Boolean> = _errorScreenVisibility

    private var _noResultsVisibility = MutableLiveData<Boolean>(true)
    var noResultsVisibility: LiveData<Boolean> = _noResultsVisibility

    private var _errorMessage = MutableLiveData<String>()
    var errorMessage: LiveData<String> = _errorMessage

    val TAG = "calltest"
    fun searchMovie(title: String){
        showSearchProgressBar(true)
        showError(false)
        viewModelScope.launch(mainDispatcher) {
              val response = tmdbApi.searchMovieSuspend(AppConstants.LANGUAGE, title, false)
             showSearchProgressBar(false)
              when (response) {
                  is NetworkResponse.Success -> {
                      Log.d(TAG, "Success")
                      _searchResult.value = response.body.results
                      showResults(response.body.results)
                  }
                  is NetworkResponse.ApiError -> {
                      Log.d("svmlog", "_____________________________")
                      Log.d("svmlog", "NetworkResponse.ApiError ->")
                      Log.d("svmlog", "code = ${response.body.cd}")
                      Log.d("svmlog", "msg = ${response.body.message}")
                      _errorMessage.value = AppConstants.API_ERROR_MESSAGE
                      showErrorScreen()
                  }
                  is NetworkResponse.NetworkError -> {
                      Log.d("svmlog", "_____________________________")
                      Log.d("svmlog", "NetworkResponse.NetworkError ->")
                      Log.d("svmlog", "cause = ${response.error.cause}")
                      Log.d("svmlog", "localizedMessage = ${response.error.localizedMessage}")
                      _errorMessage.value = AppConstants.NETWORK_ERROR_MESSAGE
                      showErrorScreen()
                  }

                  is NetworkResponse.UnknownError -> {
                      Log.d("svmlog", "_____________________________")
                      Log.d("svmlog", "NetworkResponse.UnknownError ->")
                      Log.d("svmlog", "cause = ${response.error?.cause}")
                      Log.d("svmlog", "localizedMessage = ${response.error?.localizedMessage}")
                      _errorMessage.value = AppConstants.UNKNOWN_ERROR_MESSAGE
                      showErrorScreen()
                  }
              }
          }
    }

    private fun showResults(results: List<MovieDTO>) {
        if (results.isNullOrEmpty()){
            showNoResultsMessage(true)
        } else {
            showNoResultsMessage(false)
        }
    }

    private fun showErrorScreen() {
        showError(true)
        showNoResultsMessage(false)
    }

    private fun showError(shouldShow: Boolean) {
        _errorScreenVisibility.value = shouldShow
    }

    private fun showSearchProgressBar(shouldShow: Boolean) {
        _searchProgressBarVisibility.value = shouldShow
    }

    private fun showNoResultsMessage(shouldShow: Boolean) {
        _noResultsVisibility.value = shouldShow
    }

}

Журнал ошибок

java.lang.NullPointerException
    at androidx.arch.core.executor.DefaultTaskExecutor.isMainThread(DefaultTaskExecutor.java:77)
    at androidx.arch.core.executor.ArchTaskExecutor.isMainThread(ArchTaskExecutor.java:116)
    at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:461)
    at androidx.lifecycle.LiveData.setValue(LiveData.java:304)
    at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
    at com.movies.allmovies.viewmodel.SearchViewModel.showSearchProgressBar(SearchViewModel.kt:96)
    at com.movies.allmovies.viewmodel.SearchViewModel.searchMovie(SearchViewModel.kt:38)
    at com.movies.allmovies.viewmodel.SearchViewModelTest.make request, returns 400(SearchViewModelTest.kt:90)
...