Skip to content

Commit

Permalink
create room db, test DAOs and add android test workflow script
Browse files Browse the repository at this point in the history
  • Loading branch information
nabilBouzineDev committed Aug 12, 2024
1 parent a6f0c98 commit e8b8f1d
Show file tree
Hide file tree
Showing 15 changed files with 381 additions and 36 deletions.
36 changes: 35 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,38 @@ jobs:
run: chmod +x gradlew

- name: Run unit tests
run: ./gradlew clean testDebug
run: ./gradlew clean testDebug

android_test_job:
name: Android Test
runs-on: macos-latest
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Grant execute permissions for gradlew
run: chmod +x gradlew

- name: Restore Cache
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Run Android Instrumentation Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: ./gradlew connectedDebugAndroidTest

- name: Upload Android Test Reports
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: android-test-reports
path: '**/build/reports/androidTests/'
108 changes: 108 additions & 0 deletions app/src/androidTest/kotlin/data/local/dao/AirportDAOTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package data.local.dao

import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nabilbdev.searchflight.data.local.dao.AirportDAO
import com.nabilbdev.searchflight.data.local.database.SearchFlightDatabase
import com.nabilbdev.searchflight.data.local.entity.Airport
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.io.IOException


private lateinit var airportDAO: AirportDAO
private lateinit var searchFlightDatabase: SearchFlightDatabase

// Sample data from the actual database
private val airport1 = Airport(
1,
"Francisco Sá Carneiro Airport",
"OPO",
5053134
)
private val airport2 = Airport(
2,
"Stockholm Arlanda Airport",
"ARN",
7494765
)

@RunWith(AndroidJUnit4::class)
class AirportDAOTest {

@Before
fun createDB() {
val context: Context = ApplicationProvider.getApplicationContext()

searchFlightDatabase =
Room.databaseBuilder(context, SearchFlightDatabase::class.java, "flight_search_test")
.createFromAsset("database/flight_search.db")
.allowMainThreadQueries() // Only for testing purposes
.build()

airportDAO = searchFlightDatabase.airportDAO()

}

@After
@Throws(IOException::class)
fun closeDB() {
searchFlightDatabase.close()
}

@Test
@Throws(IOException::class)
fun daoGetAllAirports_returnAllAirports() = runBlocking {
val allAirports = airportDAO.getAllAirports().first()

assertTrue(allAirports.isNotEmpty())
assertEquals(allAirports[0], airport1)
assertEquals(allAirports[1], airport2)
}

@Test
@Throws(IOException::class)
fun daoGetAirportByCode_returnSingleAirport() = runBlocking {
val airportResultOne = airportDAO.getAirportByCode("OPO")
val airportResultTwo = airportDAO.getAirportByCode("ARN")

assertEquals(airportResultOne.first(), airport1)
assertEquals(airportResultTwo.first(), airport2)
}

@Test
@Throws(IOException::class)
fun daoGetAirportExcept_verifyNotExistInReturnedAirports() = runBlocking {
val allAirports = airportDAO.getAllAirportsExcept("OPO").first()

assertTrue(airport1 !in allAirports) // airport1 is not in allAirports
assertTrue(airport2 in allAirports)
}

@Test
@Throws(IOException::class)
fun daoGetAirportByQuery_verifyNotReturnAllResultsFromDB() = runBlocking {
val query = "FR"
val allAirportsByQuery = airportDAO.getAirportsByQuery("%$query%").first()
val allAirports = airportDAO.getAllAirports().first()

assertTrue(allAirports.size > allAirportsByQuery.size)
}

@Test
@Throws(IOException::class)
fun daoGetAirportByQuery_verifyReturnedAirportsIncludeQuery() = runBlocking {
val query = "FR"
val allAirportsByQuery = airportDAO.getAirportsByQuery("%$query%").first()

assertTrue(allAirportsByQuery[0].name.contains(query, ignoreCase = true))
assertTrue(allAirportsByQuery[1].name.contains(query, ignoreCase = true))
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.nabilbdev.searchflight
package data.local.dao

import android.content.Context
import androidx.room.Room
Expand All @@ -10,7 +10,7 @@ import com.nabilbdev.searchflight.data.local.entity.Favorite
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -50,14 +50,37 @@ class FavoriteDAOTest {
}

private suspend fun addTwoFavoriteToDB() {
favoriteDAO.insert(favorite1)
favoriteDAO.insert(favorite2)
}

@Test
@Throws(IOException::class)
fun daoInsert_InsertFavoritesToDB() = runBlocking {
fun daoInsert_insertFavoritesToDB() = runBlocking {
addOneFavoriteToDB()
val allItems = favoriteDAO.getAllFavoriteAirports().first()
Assert.assertEquals(allItems[0], favorite1)
val allFavorites = favoriteDAO.getAllFavoriteAirports().first()

assertEquals(allFavorites[0], favorite1)
}

@Test
@Throws(IOException::class)
fun daoDelete_deleteFavoritesFromDB() = runBlocking {
addTwoFavoriteToDB()
favoriteDAO.delete(favorite1)
favoriteDAO.delete(favorite2)

val allFavorites = favoriteDAO.getAllFavoriteAirports().first()
assertTrue(allFavorites.isEmpty())
}

@Test
@Throws(IOException::class)
fun daoGetAllFavorites_returnAllFavoritesFromDB() = runBlocking {
addTwoFavoriteToDB()
val allFavorites = favoriteDAO.getAllFavoriteAirports().first()

assertEquals(allFavorites[0], favorite1)
assertEquals(allFavorites[1], favorite2)
}
}
10 changes: 9 additions & 1 deletion app/src/main/java/com/nabilbdev/searchflight/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.nabilbdev.searchflight.ui.theme.SearchFlightTheme

Expand All @@ -20,7 +23,12 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.surface
) {
// Code Here...
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(text = "Room database is ready!")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.nabilbdev.searchflight.data.di

import android.content.Context
import com.nabilbdev.searchflight.data.local.database.SearchFlightDatabase
import com.nabilbdev.searchflight.data.repository.OfflineSearchFlightRepository
import com.nabilbdev.searchflight.data.repository.SearchFlightRepository
import com.nabilbdev.searchflight.data.local.repository.OfflineSearchFlightRepository
import com.nabilbdev.searchflight.data.local.repository.SearchFlightRepository

interface AppContainer {
val searchFlightRepository: SearchFlightRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ interface AirportDAO {
fun getAllAirportsExcept(airportCode: String): Flow<List<Airport>>

@Query("SELECT * FROM airport WHERE iata_code = :airportCode ")
fun getAirportsByCode(airportCode: String): Flow<Airport>
fun getAirportByCode(airportCode: String): Flow<Airport>

@Query("SELECT * FROM airport")
fun getAllAirports(): Flow<List<Airport>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.nabilbdev.searchflight.data.local.entity.Airport
import com.nabilbdev.searchflight.data.local.entity.Favorite
import kotlinx.coroutines.flow.Flow

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract class SearchFlightDatabase : RoomDatabase() {
fun getDatabase(context: Context): SearchFlightDatabase {
return Instance ?: synchronized(this) {
Room.databaseBuilder(context, SearchFlightDatabase::class.java, "flight_search")
.createFromAsset("/database/flight_search.db")
.createFromAsset("database/flight_search.db")
.fallbackToDestructiveMigration()
.build()
.also { Instance = it }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import androidx.room.PrimaryKey

@Entity(tableName = "airport")
data class Airport(
@PrimaryKey val id: Int,
@PrimaryKey
val id: Int,
val name: String,
@ColumnInfo(name = "iata_code") val iataCode: String,
@ColumnInfo(name = "iata_code")
val iataCode: String,
val passengers: Int,
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.nabilbdev.searchflight.data.repository
package com.nabilbdev.searchflight.data.local.repository

import com.nabilbdev.searchflight.data.local.dao.AirportDAO
import com.nabilbdev.searchflight.data.local.dao.FavoriteDAO
Expand All @@ -12,10 +12,12 @@ interface SearchFlightRepository {

fun getAllAirportsExceptStream(airportCode: String): Flow<List<Airport>>

fun getAirportsByCodeStream(airportCode: String): Flow<Airport>
fun getAirportByCodeStream(airportCode: String): Flow<Airport?>

fun getAllFavoriteAirportsStream(): Flow<List<Favorite>>

fun getAllAirportsStream(): Flow<List<Airport>>

suspend fun insertFavoriteAirport(favorite: Favorite)

suspend fun deleteFavoriteAirport(favorite: Favorite)
Expand All @@ -32,12 +34,15 @@ class OfflineSearchFlightRepository(
override fun getAllAirportsExceptStream(airportCode: String): Flow<List<Airport>> =
airportDAO.getAllAirportsExcept(airportCode)

override fun getAirportsByCodeStream(airportCode: String): Flow<Airport> =
airportDAO.getAirportsByCode(airportCode)
override fun getAirportByCodeStream(airportCode: String): Flow<Airport?> =
airportDAO.getAirportByCode(airportCode)

override fun getAllFavoriteAirportsStream(): Flow<List<Favorite>> =
favoriteDAO.getAllFavoriteAirports()

override fun getAllAirportsStream(): Flow<List<Airport>> =
airportDAO.getAllAirports()

override suspend fun insertFavoriteAirport(favorite: Favorite) =
favoriteDAO.insert(favorite)

Expand Down
18 changes: 0 additions & 18 deletions app/src/test/java/com/nabilbdev/searchflight/ExampleUnitTest.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.nabilbdev.searchflight.data.local.fake

import com.nabilbdev.searchflight.data.local.dao.AirportDAO
import com.nabilbdev.searchflight.data.local.entity.Airport
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flow

class FakeAirportDAO : AirportDAO {
private val airports = FakeDataSource.fakeAirports

/**
* We mimic the same SQLITE query behaviour exist in the [AirportDAO].
*
* f.e: LIKE %query% changed to it.name.contains(query)
*/
override fun getAirportsByQuery(query: String): Flow<List<Airport>> = flow {

emit(
airports.filter {
it.name.contains(query, ignoreCase = true) || it.iataCode.contains(query)
}
)
}

override fun getAllAirportsExcept(airportCode: String): Flow<List<Airport>> = flow {
emit(airports.filter { it.iataCode != airportCode })
}

override fun getAirportByCode(airportCode: String): Flow<Airport> = flow {
airports.find { it.iataCode == airportCode }?.let { emit(it) }
}.filterNotNull()

override fun getAllAirports(): Flow<List<Airport>> = flow {
emit(airports)
}
}
Loading

0 comments on commit e8b8f1d

Please sign in to comment.