Skip to content

Commit

Permalink
Merge pull request #34 from Architect-Coders/feature/ui_tests
Browse files Browse the repository at this point in the history
Create UITest
  • Loading branch information
PabloJC authored Mar 10, 2020
2 parents a50c0f3 + 1592d10 commit f2348f7
Show file tree
Hide file tree
Showing 14 changed files with 381 additions and 24 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ dependencies {

testImplementation testLibs.values()
testImplementation androidxTestLibs.values()
androidTestImplementation androidTestLibs.values()
}
20 changes: 20 additions & 0 deletions app/src/androidTest/assets/product_detail.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"status_verbose": "product found",
"product": {
"product_name": "Coca Cola",
"image_small_url": "https://static.openfoodfacts.org/images/products/544/900/000/0996/front_es.446.200.jpg",
"stores": "Tesco,Auchan,Carrefour",
"quantity": "330 ml",
"categories": "Beverages, Carbonated drinks, Sodas, Non-Alcoholic beverages, Colas, Sweetened beverages, pl:zawiera-kofeinę",
"image_front_url": "https://static.openfoodfacts.org/images/products/544/900/000/0996/front_es.446.400.jpg",
"brands": "Coca-Cola",
"image_nutrition_url": "https://static.openfoodfacts.org/images/products/544/900/000/0996/nutrition_es.218.400.jpg",
"image_ingredients_url": "https://static.openfoodfacts.org/images/products/544/900/000/0996/ingredients_es.215.400.jpg",
"ingredients_text": "water, sugar, carbon dioxide, dye e150d, acidifier e338, natural flavors including caffeine,",
"countries": "Algérie,Autriche,Belgique,Brésil,Cameroun,Canada,France,Géorgie,Allemagne,Inde,Italie,Luxembourg,Mali,Martinique,Mexique,Maroc,Pays-Bas,Nouvelle-Calédonie,Pologne,Portugal,La Réunion,Arabie saoudite,Sénégal,Espagne,Suisse,Tunisie,Royaume-Uni,États-Unis, en:romania, en:ireland, en:andorra",
"code": "5449000000996",
"generic_name": "Sparkling Soft Drink with Vegetable Extracts"
},
"code": "5449000000996",
"status": 1
}
128 changes: 128 additions & 0 deletions app/src/androidTest/assets/search_products.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
{
"page_size": "20",
"count": 149845,
"products": [
{
"quantity": "300 g ℮, (15 biscuits de 20 g)",
"code": "7622210449283",
"product_name": "Prince: Goût Chocolat au Blé Complet",
"image_small_url": "https://static.openfoodfacts.org/images/products/762/221/044/9283/front_fr.286.200.jpg"
},
{
"code": "5449000000996",
"quantity": "330 ml",
"product_name": "Coca Cola",
"image_small_url": "https://static.openfoodfacts.org/images/products/544/900/000/0996/front_es.446.200.jpg"
},
{
"quantity": "400 g",
"code": "3017620422003",
"product_name": "Nutella",
"image_small_url": "https://static.openfoodfacts.org/images/products/301/762/042/2003/front_fr.168.200.jpg"
},
{
"product_name": "Coca Cola Zero",
"quantity": "330 ml",
"code": "5449000131805",
"image_small_url": "https://static.openfoodfacts.org/images/products/544/900/013/1805/front_bg.275.200.jpg"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/800/050/031/0427/front_fr.29.200.jpg",
"quantity": "304g",
"code": "8000500310427",
"product_name": "Nutella biscuits"
},
{
"product_name": "Biscuité vanille",
"quantity": "350 g",
"code": "16130357",
"image_small_url": "https://static.openfoodfacts.org/images/products/16130357/front_fr.239.200.jpg"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/541/018/803/1072/front_fr.30.200.jpg",
"quantity": "1 L",
"code": "5410188031072",
"product_name": "Gazpacho"
},
{
"code": "3033710065967",
"quantity": "1 kg",
"product_name": "Nesquik",
"image_small_url": "https://static.openfoodfacts.org/images/products/303/371/006/5967/front_fr.80.200.jpg"
},
{
"product_name": "Nutella",
"code": "3017620421006",
"quantity": "750 g",
"image_small_url": "https://static.openfoodfacts.org/images/products/301/762/042/1006/front_fr.158.200.jpg"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/322/982/012/9488/front_fr.94.200.jpg",
"product_name": "Muesli sans sucre ajouté* Bio",
"code": "3229820129488",
"quantity": "375 g ℮"
},
{
"product_name": "Nesquik",
"code": "3033710065066",
"quantity": "250 g",
"image_small_url": "https://static.openfoodfacts.org/images/products/303/371/006/5066/front_es.130.200.jpg"
},
{
"quantity": "100 g ℮",
"code": "3046920022606",
"product_name": "Chocolat Noir 85% Cacao",
"image_small_url": "https://static.openfoodfacts.org/images/products/304/692/002/2606/front_es.85.200.jpg"
},
{
"product_name": "Coca-Cola",
"code": "5449000267412",
"quantity": "1,25 l",
"image_small_url": "https://static.openfoodfacts.org/images/products/544/900/026/7412/front_fr.36.200.jpg"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/315/947/000/0120/front_fr.83.200.jpg",
"product_name": "Corn Flakes",
"quantity": "500 g",
"code": "3159470000120"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/301/762/042/9484/front_es.228.200.jpg",
"quantity": "825 g",
"code": "3017620429484",
"product_name": "Nutella"
},
{
"code": "7613034626844",
"quantity": "430 g",
"product_name": "Chocapic",
"image_small_url": "https://static.openfoodfacts.org/images/products/761/303/462/6844/front_es.102.200.jpg"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/541/004/100/1204/front_es.178.200.jpg",
"code": "5410041001204",
"quantity": "100 g",
"product_name": "TUC original"
},
{
"quantity": "100 g",
"code": "3045140105502",
"product_name": "Alpenmilch",
"image_small_url": "https://static.openfoodfacts.org/images/products/304/514/010/5502/front_es.185.200.jpg"
},
{
"image_small_url": "https://static.openfoodfacts.org/images/products/316/893/001/0265/front_fr.94.200.jpg",
"product_name": "Cruesli",
"quantity": "450 g ℮",
"code": "3168930010265"
},
{
"quantity": "500 ml",
"code": "8715700407760",
"product_name": "Tomato Ketchup",
"image_small_url": "https://static.openfoodfacts.org/images/products/871/570/040/7760/front_de.75.200.jpg"
}
],
"page": "1",
"skip": 0
}
95 changes: 95 additions & 0 deletions app/src/androidTest/java/com/pabji/myfridge/ui/UITest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.pabji.myfridge.ui

import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.pressBack
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import com.jakewharton.espresso.OkHttp3IdlingResource
import com.pabji.myfridge.R
import com.pabji.myfridge.model.network.api.RetrofitApiClient
import com.pabji.myfridge.ui.main.MainActivity
import com.pabji.myfridge.ui.rules.MockWebServerRule
import com.pabji.myfridge.utils.fromJson
import okhttp3.mockwebserver.MockResponse
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.koin.test.KoinTest
import org.koin.test.get

class UITest : KoinTest {

@get:Rule
val mockWebServerRule = MockWebServerRule()

@get:Rule
val activityTestRule = ActivityTestRule(MainActivity::class.java, false, false)

@get:Rule
val grantPermissionRule: GrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.CAMERA"
)

@Before
fun setUp() {
mockWebServerRule.server.enqueue(MockResponse().fromJson("search_products.json"))
mockWebServerRule.server.enqueue(MockResponse().fromJson("product_detail.json"))
mockWebServerRule.server.enqueue(MockResponse().fromJson("product_detail.json"))
val resource = OkHttp3IdlingResource.create("OkHttp", get<RetrofitApiClient>().okHttpClient)
IdlingRegistry.getInstance().register(resource)
}

@Test
fun onSearchProducts() {
activityTestRule.launchActivity(null)

onView(
allOf(
withId(R.id.action_search),
isDescendantOfA(withId(R.id.bottom_navigation))
)
).perform(click())

onView(withId(R.id.fab)).check(matches(not(isDisplayed())))

onView(withId(R.id.rv_product_list)).perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
1,
click()
)
)

onView(withId(R.id.btn_add)).perform(click())

onView(isRoot()).perform(pressBack())

onView(
allOf(
withId(R.id.action_fridge),
isDescendantOfA(withId(R.id.bottom_navigation))
)
).perform(click())

onView(withId(R.id.fab)).check(matches(isDisplayed()))

onView(withId(R.id.rv_product_list)).perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
click()
)
)

onView(isRoot()).perform(pressBack())

onView(withId(R.id.fab)).perform(click())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.pabji.myfridge.ui.rules

import okhttp3.mockwebserver.MockWebServer
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import org.koin.core.context.loadKoinModules
import org.koin.core.qualifier.named
import org.koin.dsl.module
import kotlin.concurrent.thread

class MockWebServerRule : TestRule {

val server = MockWebServer()

override fun apply(base: Statement?, description: Description?) = object : Statement() {
override fun evaluate() {
server.start()
replaceBaseUrl()
base?.evaluate()
server.shutdown()
}
}

private fun replaceBaseUrl() {
val testModule = module {
single(named("baseUrl"), override = true) { askMockServerUrlOnAnotherThread() }
}
loadKoinModules(testModule)
}

private fun askMockServerUrlOnAnotherThread(): String {
/*
This needs to be done immediately, but the App will crash with
"NetworkOnMainThreadException" if this is not extracted from the main thread. So this is
a "hack" to prevent it. We don't care about blocking in a test, and it's fast.
*/
var url = ""
val t = thread {
url = server.url("/").toString()
}
t.join()
return url
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.pabji.myfridge.utils

import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.mockwebserver.MockResponse
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets.UTF_8

fun MockResponse.fromJson(jsonFile: String): MockResponse =
setBody(readJsonFile(jsonFile))

private fun readJsonFile(jsonFilePath: String): String {
val context = InstrumentationRegistry.getInstrumentation().context

var br: BufferedReader? = null

try {
br = BufferedReader(
InputStreamReader(
context.assets.open(
jsonFilePath
), UTF_8
)
)
var line: String?
val text = StringBuilder()

do {
line = br.readLine()
line?.let { text.append(line) }
} while (line != null)
br.close()
return text.toString()
} finally {
br?.close()
}
}
7 changes: 5 additions & 2 deletions app/src/main/java/com/pabji/myfridge/di/Modules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@ fun Application.initDI() {
}
}

val BASE_URL = named("baseUrl")

val appModule = module {
single {
Room.databaseBuilder(get(), RoomDatabase::class.java, "products.db")
Room.databaseBuilder(get(), RoomDatabase::class.java, "products1.db")
.fallbackToDestructiveMigration().build()
}
single { RetrofitApiClient.createService() }
factory<LocalDatasource> { RoomDataSource(get()) }
factory<RemoteDatasource> { RetrofitDataSource(get()) }
single<CoroutineDispatcher> { Dispatchers.Main }
single(BASE_URL) { "https://es.openfoodfacts.org" }
single { RetrofitApiClient(get(BASE_URL)) }
}

val dataModule = module {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import com.pabji.domain.DetailError
import com.pabji.domain.Either
import com.pabji.domain.SearchError
import com.pabji.myfridge.model.network.api.DETAIL_FIELDS
import com.pabji.myfridge.model.network.api.RetrofitApiService
import com.pabji.myfridge.model.network.api.RetrofitApiClient
import com.pabji.myfridge.model.network.api.SIMPLE_FIELDS
import com.pabji.myfridge.model.network.responses.toProduct

class RetrofitDataSource(private val apiService: RetrofitApiService) : RemoteDatasource {
class RetrofitDataSource(private val apiClient: RetrofitApiClient) : RemoteDatasource {

override suspend fun searchProducts(searchTerm: String?) =
with(
apiService.searchProductsByName(
apiClient.service.searchProductsByName(
searchTerm,
fields = SIMPLE_FIELDS.joinToString(",")
)
Expand All @@ -26,7 +26,7 @@ class RetrofitDataSource(private val apiService: RetrofitApiService) : RemoteDat
}

override suspend fun getProductByBarcode(barcode: String) =
with(apiService.getProductDetailById(barcode, DETAIL_FIELDS.joinToString(","))) {
with(apiClient.service.getProductDetailById(barcode, DETAIL_FIELDS.joinToString(","))) {
if (isSuccessful) {
body()?.product?.run {
Either.Right(toProduct())
Expand Down
Loading

0 comments on commit f2348f7

Please sign in to comment.