Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dependencies and configuration of testing frameworks #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ android {
versionName = "2.0.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["androidx.test.uiautomator.debug.UiAutomatorTimeout"] = "60000" // Increase timeout in milliseconds (default 20000)
vectorDrawables {
useSupportLibrary = true
}
Expand All @@ -43,6 +44,18 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
testOptions {
unitTests.isIncludeAndroidResources = true
execution = "ANDROIDX_TEST_ORCHESTRATOR"
}
tasks.withType<Test> {
useJUnitPlatform()
// options.parallel = false // Disable parallel downloading
}
packagingOptions {
resources.excludes.add("META-INF/LICENSE.md")
resources.excludes.add("META-INF/LICENSE-notice.md")
}
kotlinOptions {
jvmTarget = "17"
}
Expand Down Expand Up @@ -93,12 +106,51 @@ dependencies {
implementation(libs.splashscreen)
implementation(libs.accompanist.systemuicontroller)
implementation(libs.hilt)
implementation(libs.hilt.testing)
implementation(libs.google.gson)
implementation(libs.androidx.uiautomator)
testImplementation(project(":core:network"))
ksp(libs.hilt.compiler)
testImplementation(libs.junit)
testImplementation(libs.junit.jupiter)
testImplementation(libs.junit.api)
testRuntimeOnly(libs.junit.engine)
testImplementation(libs.robolectric)
testImplementation(libs.mockito.core)
testImplementation(libs.mockito.junit.jupiter)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlinx.coroutines.core)
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.core.testing)
testImplementation(libs.nhaarman.mockito.kotlin)
testImplementation(libs.lifecycle.common)
androidTestImplementation(libs.kotlinx.coroutines.core)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.junit.api)
androidTestImplementation(libs.junit.engine)
androidTestImplementation("de.mannodermaus.junit5:android-test-core:1.3.0")
androidTestImplementation("de.mannodermaus.junit5:android-test-runner:1.3.0")
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.rules)
runtimeOnly(libs.androidx.test.uiautomator) // exclude group: 'androidx.test', module: 'uiautomator'
androidTestImplementation(libs.androidx.test.orchestrator)
androidTestUtil(libs.androidx.test.orchestrator)
androidTestImplementation(libs.espresso.core)
androidTestImplementation(libs.mockito.core)
androidTestImplementation(libs.mockito.android)
androidTestImplementation(libs.mockito.junit.jupiter)
androidTestImplementation(platform(libs.compose.bom))
androidTestImplementation(libs.ui.test.junit4)
androidTestImplementation(project(":core:network"))
androidTestImplementation(libs.navigation.testing)
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
debugImplementation("androidx.test:monitor:1.6.0") {
isTransitive = false
}
}

tasks.withType<Test> {
useJUnitPlatform()
}
68 changes: 68 additions & 0 deletions app/src/androidTest/java/com/mikelau/pokedex/PokemonScreenTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.mikelau.pokedex

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.navigation.NavController
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.mikelau.core.common.UiEvents
import com.mikelau.feature.pokemon.domain.repository.PokemonRepository
import com.mikelau.feature.pokemon.domain.usecase.GetPokemonListUseCase
import com.mikelau.feature.pokemon.ui.screen.PokemonScreen
import com.mikelau.feature.pokemon.ui.screen.PokemonViewModel
import kotlinx.coroutines.flow.flow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations

@RunWith(AndroidJUnit4::class)
class PokemonScreenTest {

@get:Rule
val composeTestRule = createComposeRule()

@Mock
private lateinit var pokemonViewModel: PokemonViewModel

@Mock
private lateinit var mockPokemonRepository: PokemonRepository

@Mock
lateinit var getPokemonListUseCase: GetPokemonListUseCase

@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}

@Test
fun myTempTest() {

// Mock the flow to emit loading and success events
val flow = flow {
emit(UiEvents.Loading())
emit(UiEvents.Success(mockPokemonRepository.getPokemonList()))
}

// Initialize the ViewModel
pokemonViewModel = PokemonViewModel(getPokemonListUseCase)

// Arrange the mock NavController
val mockNavController = Mockito.mock(NavController::class.java)

// Start the app
composeTestRule.setContent {
PokemonScreen(
pokemonViewModel,
mockNavController
)
}

// Perform actions and assertions
composeTestRule.onNodeWithText("Pokédex").assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.mikelau.pokedex

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.mikelau.core.common.UiEvents
import com.mikelau.feature.pokemon.data.mapper.toDomainPokemonList
import com.mikelau.feature.pokemon.domain.model.Pokemon
import com.mikelau.feature.pokemon.domain.repository.PokemonRepository
import com.mikelau.feature.pokemon.domain.usecase.GetPokemonListUseCase
import com.mikelau.feature.pokemon.ui.screen.PokemonViewModel
import com.mikelau.pokedex.api.FakeApiService
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Rule
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.stubbing.Answer
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode

@HiltAndroidTest
@Config(sdk = [25], application = HiltTestApplication::class, manifest= Config.NONE)
@ExperimentalCoroutinesApi
@LooperMode(LooperMode.Mode.PAUSED)
@ExtendWith(MockitoExtension::class)
class GetPokemonListUseCaseJUnit5Test {

@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()

@Mock
private lateinit var mockPokemonRepository: PokemonRepository

@Mock
private lateinit var getPokemonListUseCase: GetPokemonListUseCase

private var pokemonList = listOf<Pokemon>()

@BindValue
@JvmField
val fakeApiService: FakeApiService = FakeApiService()

private lateinit var pokemonViewModel: PokemonViewModel

companion object {

private val dispatcher = TestCoroutineDispatcher()

@JvmStatic
@BeforeAll
fun setupClass() {
// Setup for the class
Dispatchers.setMain(dispatcher)
}

@JvmStatic
@AfterAll
fun tearDownClass() {
// Teardown for the class
Dispatchers.resetMain()
}
}

@BeforeEach
fun setup() {

val pokemonList = listOf<Pokemon>() // ... populate pokemonList

val expectedFlow = flow {
emit(UiEvents.Loading())
emit(UiEvents.Success(pokemonList))
}

Mockito.`when`(getPokemonListUseCase.invoke()).thenReturn(expectedFlow)
pokemonViewModel = PokemonViewModel(getPokemonListUseCase)
}

@Test
fun testUseCaseFlowEmission() {
// Define an Answer to mock flow emission
val flowAnswer = Answer<Flow<UiEvents<List<Pokemon>>>> { invocation ->
val flow = flow {
emit(UiEvents.Loading())
emit(UiEvents.Success(mockPokemonRepository.getPokemonList()))
}
flow
}

// Extract the actual Flow<String> from the Answer
val flowToEmit = flowAnswer.answer(null) // Provide null argument (optional)

// Configure mock behavior using the Answer
Mockito.`when`(getPokemonListUseCase.invoke()).thenReturn(flowToEmit)

runTest {

pokemonList = fakeApiService.getPokemonList().toDomainPokemonList()

Mockito.`when`(mockPokemonRepository.getPokemonList()).thenReturn(pokemonList)

// Use the mock use case in your ViewModel test logic
pokemonViewModel = PokemonViewModel(getPokemonListUseCase)
pokemonViewModel.getPokemonList()

val value = pokemonViewModel.pokemonList.value

org.junit.jupiter.api.Assertions.assertNotNull(value.data)

}
}
}
27 changes: 27 additions & 0 deletions app/src/test/java/com/mikelau/pokedex/JsonProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mikelau.pokedex

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

object JsonProvider {

inline fun <reified U> objectFromJsonFileWithType(filePath: String): U =
Gson().fromJson(readResourceFile(filePath), object : TypeToken<U>() {}.type)

fun readResourceFile(fileName: String): String? {
val inputStream = this::class.java.getResourceAsStream(fileName)
return if (inputStream != null) {
try {
inputStream.bufferedReader()
.use { it.readText() }
} catch (e: Exception) {
e.printStackTrace()
null
}
} else {
println("Error: Resource not found.")
null
}
}

}
34 changes: 34 additions & 0 deletions app/src/test/java/com/mikelau/pokedex/api/FakeApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.mikelau.pokedex.api

import com.mikelau.core.network.ApiService
import com.mikelau.core.network.model.PokemonDetailsDto
import com.mikelau.core.network.model.PokemonDto
import com.mikelau.core.network.model.PokemonListResponse
import com.mikelau.pokedex.JsonProvider
import java.lang.Exception
import javax.inject.Inject

class FakeApiService @Inject constructor() : ApiService {

var failUserApi: Boolean = false
var wrongResponse: Boolean = false

override suspend fun getPokemonList(): PokemonListResponse {
if (failUserApi) throw Exception("Api failed")
val fakeResponse: PokemonListResponse = JsonProvider.objectFromJsonFileWithType(POKEMON_LIST_JSON)

if (wrongResponse) return fakeResponse.apply {
results = listOf(PokemonDto("", ""))
}

return fakeResponse
}

override suspend fun getPokemonDetails(id: String): PokemonDetailsDto {
TODO("Not yet implemented")
}

companion object {
private const val POKEMON_LIST_JSON = "/json/pokemon-list.json"
}
}
Loading