Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
Integration tests (#2896)
Browse files Browse the repository at this point in the history
* WIP: Setup integration tests (androidTest)

* WIP: Setup Room migration tests

* Working room DB migration configuration

* Add DB migrations tests

* Add migration tests for 123 to 125

* Add GitHub Action workflow that runs the integration tests

* WIP: Fix Android linkage problems & the new workflow

* Add missing dependency

* Make the workflow run only for :ivy-data

* Fix Detekt errors

* Fix the Integration tests workflow #1

* Rename steps
  • Loading branch information
ILIYANGERMANOV authored Jan 27, 2024
1 parent 632aca0 commit d41c3f5
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 28 deletions.
45 changes: 45 additions & 0 deletions .github/workflows/integration_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Integration tests (androidTest)

on:
push:
branches:
- main
pull_request:

jobs:
integration_test:
runs-on: ubuntu-latest
steps:
- name: Checkout GIT
uses: actions/checkout@v4

- name: Setup Java SDK
uses: actions/setup-java@v4
with:
distribution: 'adopt'
java-version: '18'

- name: Enable Gradle Wrapper caching (optimization)
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Ivy Wallet's code
run: ./gradlew ivy-data:assembleDebug ivy-data:assembleAndroidTest

- name: Enable KVM (emulator optimization)
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run android tests on Emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew ivy-data:connectedDebugAndroidTest
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ captures/
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
.idea/androidTestResultsUserPreferences.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
.idea/codeStyles
Expand Down Expand Up @@ -93,4 +94,4 @@ lint/tmp/
lint/reports/

# JS
node_modules/*
node_modules/*
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
android:name="android.appwidget.provider"
android:resource="@xml/wallet_balance_widget_info" />
</receiver>

<service
android:name=".PaymentTileService"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<resources>

<!-- Base application theme. -->

<style name="AppTheme" parent="Theme.MaterialComponents.NoActionBar">
<item name="android:colorPrimary">@color/ivy</item>
<item name="android:colorPrimaryDark">@color/ivy</item>
Expand Down
1 change: 1 addition & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {
implementation(libs.kotlinx.serialization.plugin)
implementation(libs.ksp.plugin)
implementation(libs.cashapp.molecule.plugin)
implementation(libs.room.plugin)

// Make version catalog available in precompiled scripts
// https://github.com/gradle/gradle/issues/15383#issuecomment-1567461389
Expand Down
14 changes: 12 additions & 2 deletions buildSrc/src/main/kotlin/ivy.room.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
plugins {
id("ivy.module")
id("androidx.room")
}

dependencies {
implementation(libs.bundles.room)
ksp(libs.room.compiler)

androidTestImplementation(libs.room.testing)
}

android {
sourceSets {
// Adds exported schema location as test app assets.
getByName("androidTest").assets.srcDirs(files("$projectDir/schemas"))
}
}

ksp {
arg("room.schemaLocation", "$projectDir/schemas")
room {
schemaDirectory("$projectDir/schemas")
}
19 changes: 17 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ hilt = "2.50"
room = "2.6.1"
androidx-work = "2.9.0"
kotlinx-collections = "0.3.7"
androidx-test = "1.4.0"

# Android
min-sdk = "28"
Expand Down Expand Up @@ -63,6 +64,11 @@ kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-te
cashapp-molecule-plugin = { module = "app.cash.molecule:molecule-gradle-plugin", version = "1.3.2" }
cashapp-turbine = { module = "app.cash.turbine:turbine", version = "1.0.0" }

# Integartion (Android) testing
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" }
androidx-test-core = { module = "androidx.test:core-ktx", version.ref = "androidx-test" }
androidx-test-ext = { module = "androidx.test.ext:junit-ktx", version = "1.1.5" }

# Compose
compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
Expand All @@ -79,11 +85,14 @@ glance = { module = "androidx.glance:glance", version.ref = "glance" }
glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "glance" }
glance-material3 = { module = "androidx.glance:glance-material3", version.ref = "glance" }

# Local persistence
datastore = { module = "androidx.datastore:datastore-preferences", version = "1.0.0" }
# Room
room-plugin = {module="androidx.room:androidx.room.gradle.plugin", version.ref = "room"}
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-testing = { module = "androidx.room:room-testing", version.ref = "room" }

datastore = { module = "androidx.datastore:datastore-preferences", version = "1.0.0" }

# Hilt
hilt = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
Expand Down Expand Up @@ -162,6 +171,12 @@ testing = [
"mockk",
"cashapp-turbine"
]
integration-testing = [
"kotest-assertions",
"androidx-test-core",
"androidx-test-runner",
"androidx-test-ext"
]
compose = [
"compose-animation",
"compose-foundation",
Expand Down
6 changes: 5 additions & 1 deletion ivy-data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ plugins {
}

android {
namespace = "com.ivy.persistence"
namespace = "com.ivy.data"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}

dependencies {
Expand All @@ -13,5 +16,6 @@ dependencies {
implementation(libs.datastore)
implementation(libs.bundles.ktor)

androidTestImplementation(libs.bundles.integration.testing)
testImplementation(projects.ivyTesting)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.ivy.data.db

import androidx.room.Room
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.ivy.data.db.migration.Migration123to124_LoanIncludeDateTime
import com.ivy.data.db.migration.Migration124to125_LoanEditDateTime
import com.ivy.data.model.LoanType
import io.kotest.matchers.shouldBe
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.UUID

@RunWith(AndroidJUnit4::class)
class IvyRoomDatabaseMigrationTest {

@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
IvyRoomDatabase::class.java,
listOf(IvyRoomDatabase.DeleteSEMigration()),
FrameworkSQLiteOpenHelperFactory()
)

@Test
fun migrate123to125_LoanDateTime() {
// given
helper.createDatabase(TestDb, 123).apply {
// Database has schema version 1. Insert some data using SQL queries.
// You can't use DAO classes because they expect the latest schema.
val insertSql = """
INSERT INTO loans (name, amount, type, color, icon, orderNum, accountId, isSynced, isDeleted, id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""".trimIndent()

// Assuming you have an instance of LoanEntity named loanEntity
val preparedStatement = compileStatement(insertSql).apply {
// Bind the values from your LoanEntity instance to the prepared statement
bindString(1, "Loan 1")
bindDouble(2, 123.50)
bindString(3, LoanType.BORROW.name) // Assuming you store enum as name
bindLong(4, 13)
bindString(5, "ic")
bindDouble(6, 3.14)
bindString(7, UUID.randomUUID().toString())
bindLong(8, 1)
bindLong(9, 0)
bindString(10, UUID.randomUUID().toString())
}
preparedStatement.executeInsert()
close()
}

// when
helper.runMigrationsAndValidate(
TestDb,
124,
true,
Migration123to124_LoanIncludeDateTime()
)
val newDb = helper.runMigrationsAndValidate(
TestDb,
125,
true,
Migration124to125_LoanEditDateTime()
)

// then
newDb.query("SELECT * FROM loans").apply {
moveToFirst() shouldBe true
getString(0) shouldBe "Loan 1"
getDouble(1) shouldBe 123.50
getString(2) shouldBe LoanType.BORROW.name
}
newDb.close()
}

@Test
fun migrateAll() {
// given:
// Create earliest version of the database:
// for Ivy Wallet versions below 106 are broken :/
helper.createDatabase(TestDb, 106).apply {
close()
}

// then:
// Open latest version of the database.
// Room validates and executes all migrations.
Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
IvyRoomDatabase::class.java,
TestDb
).addMigrations(*IvyRoomDatabase.migrations()).build().apply {
openHelper.writableDatabase.close()
}
}

companion object {
private const val TestDb = "migration-test"
}
}
45 changes: 24 additions & 21 deletions ivy-data/src/main/java/com/ivy/data/db/IvyRoomDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,34 +96,37 @@ abstract class IvyRoomDatabase : RoomDatabase() {
companion object {
const val DB_NAME = "ivywallet.db"

fun migrations() = arrayOf(
Migration105to106_TrnRecurringRules(),
Migration106to107_Wishlist(),
Migration107to108_Sync(),
Migration108to109_Users(),
Migration109to110_PlannedPayments(),
Migration110to111_PlannedPaymentRule(),
Migration111to112_User_testUser(),
Migration112to113_ExchangeRates(),
Migration113to114_Multi_Currency(),
Migration114to115_Category_Account_Icons(),
Migration115to116_Account_Include_In_Balance(),
Migration116to117_SalteEdgeIntgration(),
Migration117to118_Budgets(),
Migration118to119_Loans(),
Migration119to120_LoanTransactions(),
Migration120to121_DropWishlistItem(),
Migration122to123_ExchangeRates(),
Migration123to124_LoanIncludeDateTime(),
Migration124to125_LoanEditDateTime()
)

@Suppress("SpreadOperator")
fun create(applicationContext: Context): IvyRoomDatabase {
return Room
.databaseBuilder(
applicationContext,
IvyRoomDatabase::class.java,
DB_NAME
)
.addMigrations(
Migration105to106_TrnRecurringRules(),
Migration106to107_Wishlist(),
Migration107to108_Sync(),
Migration108to109_Users(),
Migration109to110_PlannedPayments(),
Migration110to111_PlannedPaymentRule(),
Migration111to112_User_testUser(),
Migration112to113_ExchangeRates(),
Migration113to114_Multi_Currency(),
Migration114to115_Category_Account_Icons(),
Migration115to116_Account_Include_In_Balance(),
Migration116to117_SalteEdgeIntgration(),
Migration117to118_Budgets(),
Migration118to119_Loans(),
Migration119to120_LoanTransactions(),
Migration120to121_DropWishlistItem(),
Migration122to123_ExchangeRates(),
Migration123to124_LoanIncludeDateTime(),
Migration124to125_LoanEditDateTime()
)
.addMigrations(*migrations())
.build()
}
}
Expand Down
4 changes: 4 additions & 0 deletions ivy-resources/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ plugins {
android {
namespace = "com.ivy.resources"
}

dependencies {
implementation(libs.material)
}

0 comments on commit d41c3f5

Please sign in to comment.