mirror of
https://github.com/bitwarden/android.git
synced 2025-12-11 04:39:19 -06:00
Add example test for MainViewModel (#113)
This commit is contained in:
parent
c8194545a4
commit
d1b5f3078e
13
.github/workflows/test.yml
vendored
13
.github/workflows/test.yml
vendored
@ -55,15 +55,26 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-build-
|
${{ runner.os }}-build-
|
||||||
|
|
||||||
|
- name: Configure Ruby
|
||||||
|
uses: ruby/setup-ruby@0cde4689ba33c09f1b890c1725572ad96751a3fc # v1.178.0
|
||||||
|
with:
|
||||||
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Configure JDK
|
- name: Configure JDK
|
||||||
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
|
||||||
with:
|
with:
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
|
||||||
|
- name: Install Fastlane
|
||||||
|
run: |
|
||||||
|
gem install bundler:2.2.27
|
||||||
|
bundle config path vendor/bundle
|
||||||
|
bundle install --jobs 4 --retry 3
|
||||||
|
|
||||||
- name: Build and test
|
- name: Build and test
|
||||||
run: |
|
run: |
|
||||||
./gradlew testDebug lintDebug
|
bundle exec fastlane check
|
||||||
|
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1
|
uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1
|
||||||
|
|||||||
@ -141,6 +141,61 @@ dependencies {
|
|||||||
androidTestImplementation(libs.bundles.tests.instrumented)
|
androidTestImplementation(libs.bundles.tests.instrumented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kover {
|
||||||
|
currentProject {
|
||||||
|
sources {
|
||||||
|
excludeJava = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reports {
|
||||||
|
filters {
|
||||||
|
excludes {
|
||||||
|
androidGeneratedClasses()
|
||||||
|
annotatedBy(
|
||||||
|
// Compose previews
|
||||||
|
"androidx.compose.ui.tooling.preview.Preview",
|
||||||
|
// Manually excluded classes/files/etc.
|
||||||
|
"com.bitwarden.authenticator.data.platform.annotation.OmitFromCoverage",
|
||||||
|
)
|
||||||
|
classes(
|
||||||
|
// Navigation helpers
|
||||||
|
"*.*NavigationKt*",
|
||||||
|
// Composable singletons
|
||||||
|
"*.*ComposableSingletons*",
|
||||||
|
// Generated classes related to interfaces with default values
|
||||||
|
"*.*DefaultImpls*",
|
||||||
|
// Databases
|
||||||
|
"*.database.*Database*",
|
||||||
|
"*.dao.*Dao*",
|
||||||
|
// Dagger Hilt
|
||||||
|
"dagger.hilt.*",
|
||||||
|
"hilt_aggregated_deps.*",
|
||||||
|
"*_Factory",
|
||||||
|
"*_Factory\$*",
|
||||||
|
"*_*Factory",
|
||||||
|
"*_*Factory\$*",
|
||||||
|
"*.Hilt_*",
|
||||||
|
"*_HiltModules",
|
||||||
|
"*_HiltModules\$*",
|
||||||
|
"*_Impl",
|
||||||
|
"*_Impl\$*",
|
||||||
|
"*_MembersInjector",
|
||||||
|
)
|
||||||
|
packages(
|
||||||
|
// Dependency injection
|
||||||
|
"*.di",
|
||||||
|
// Models
|
||||||
|
"*.model",
|
||||||
|
// Custom UI components
|
||||||
|
"com.bitwarden.authenticator.ui.platform.components",
|
||||||
|
// Theme-related code
|
||||||
|
"com.bitwarden.authenticator.ui.platform.theme",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protobuf {
|
protobuf {
|
||||||
protoc {
|
protoc {
|
||||||
artifact = libs.google.protobuf.protoc.get().toString()
|
artifact = libs.google.protobuf.protoc.get().toString()
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
package com.bitwarden.authenticator
|
||||||
|
|
||||||
|
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||||
|
import com.bitwarden.authenticator.ui.platform.base.BaseViewModelTest
|
||||||
|
import com.bitwarden.authenticator.ui.platform.feature.settings.appearance.model.AppTheme
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
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
|
||||||
|
|
||||||
|
class MainViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
||||||
|
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(false)
|
||||||
|
private val settingsRepository = mockk<SettingsRepository> {
|
||||||
|
every { appTheme } returns AppTheme.DEFAULT
|
||||||
|
every { appThemeStateFlow } returns mutableAppThemeFlow
|
||||||
|
every { isScreenCaptureAllowedStateFlow } returns mutableScreenCaptureAllowedFlow
|
||||||
|
}
|
||||||
|
private lateinit var mainViewModel: MainViewModel
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
mainViewModel = MainViewModel(settingsRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `on AppThemeChanged should update state`() {
|
||||||
|
assertEquals(
|
||||||
|
MainState(
|
||||||
|
theme = AppTheme.DEFAULT,
|
||||||
|
),
|
||||||
|
mainViewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
mainViewModel.trySendAction(
|
||||||
|
MainAction.Internal.ThemeUpdate(
|
||||||
|
theme = AppTheme.DARK,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
MainState(
|
||||||
|
theme = AppTheme.DARK,
|
||||||
|
),
|
||||||
|
mainViewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
settingsRepository.appTheme
|
||||||
|
settingsRepository.appThemeStateFlow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package com.bitwarden.authenticator.ui.platform.base
|
||||||
|
|
||||||
|
import app.cash.turbine.ReceiveTurbine
|
||||||
|
import app.cash.turbine.TurbineContext
|
||||||
|
import app.cash.turbine.turbineScope
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension
|
||||||
|
|
||||||
|
abstract class BaseViewModelTest {
|
||||||
|
@Suppress("unused", "JUnitMalformedDeclaration")
|
||||||
|
@RegisterExtension
|
||||||
|
protected open val mainDispatcherExtension = MainDispatcherExtension()
|
||||||
|
|
||||||
|
protected suspend fun <S : Any, E : Any, T : BaseViewModel<S, E, *>> T.stateEventFlow(
|
||||||
|
backgroundScope: CoroutineScope,
|
||||||
|
validate: suspend TurbineContext.(
|
||||||
|
stateFlow: ReceiveTurbine<S>,
|
||||||
|
eventFlow: ReceiveTurbine<E>,
|
||||||
|
) -> Unit,
|
||||||
|
) {
|
||||||
|
turbineScope {
|
||||||
|
this.validate(
|
||||||
|
this@stateEventFlow.stateFlow.testIn(backgroundScope),
|
||||||
|
this@stateEventFlow.eventFlow.testIn(backgroundScope),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
package com.bitwarden.authenticator.ui.platform.base
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestDispatcher
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import kotlinx.coroutines.test.resetMain
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import org.junit.jupiter.api.extension.AfterAllCallback
|
||||||
|
import org.junit.jupiter.api.extension.AfterEachCallback
|
||||||
|
import org.junit.jupiter.api.extension.BeforeAllCallback
|
||||||
|
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit 5 Extension for automatically setting a [testDispatcher] as the "main" dispatcher.
|
||||||
|
*
|
||||||
|
* Note that this may be used as a normal class property with [RegisterExtension] or may be applied
|
||||||
|
* directly to a test class using [ExtendWith].
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class MainDispatcherExtension(
|
||||||
|
private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
|
||||||
|
) : AfterAllCallback,
|
||||||
|
AfterEachCallback,
|
||||||
|
BeforeAllCallback,
|
||||||
|
BeforeEachCallback {
|
||||||
|
override fun afterAll(context: ExtensionContext?) {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterEach(context: ExtensionContext?) {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeAll(context: ExtensionContext?) {
|
||||||
|
Dispatchers.setMain(testDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeEach(context: ExtensionContext?) {
|
||||||
|
Dispatchers.setMain(testDispatcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,7 +19,7 @@ platform :android do
|
|||||||
|
|
||||||
desc "Runs tests"
|
desc "Runs tests"
|
||||||
lane :check do
|
lane :check do
|
||||||
gradle(task: "check")
|
gradle(tasks: ["check","koverXmlReportDebug"])
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Apply build version information"
|
desc "Apply build version information"
|
||||||
|
|||||||
@ -47,7 +47,7 @@ kotlinCompilerExtensionVersion = "1.5.14"
|
|||||||
kotlinxCollectionsImmutable = "0.3.7"
|
kotlinxCollectionsImmutable = "0.3.7"
|
||||||
kotlinxCoroutines = "1.8.1"
|
kotlinxCoroutines = "1.8.1"
|
||||||
kotlinxSerialization = "1.6.3"
|
kotlinxSerialization = "1.6.3"
|
||||||
kotlinxKover = "0.7.6"
|
kotlinxKover = "0.8.0"
|
||||||
ksp = "1.9.24-1.0.20"
|
ksp = "1.9.24-1.0.20"
|
||||||
mockk = "1.13.10"
|
mockk = "1.13.10"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user