From 6c4d841c30980d603b7844b8692303d09c74eedd Mon Sep 17 00:00:00 2001 From: Guillaume EHRET Date: Wed, 10 Apr 2019 09:13:00 +0200 Subject: [PATCH] STEP 9 : write integration tests to check our screens --- .../devmind/devoxx/ExampleInstrumentedTest.kt | 24 ---- .../com/devmind/devoxx/MainActivityTest.kt | 24 ++++ .../devmind/devoxx/SpeakerListActivityTest.kt | 76 +++++++++++++ .../devoxx/SpeakerListViewModelTest.kt | 60 ++++++++++ .../devmind/devoxx/SpeakerViewModelTest.kt | 104 ++++++++++++++++++ .../devmind/devoxx/model/SpeakerDaoTest.kt | 63 +++++++++++ 6 files changed, 327 insertions(+), 24 deletions(-) delete mode 100644 app/src/androidTest/java/com/devmind/devoxx/ExampleInstrumentedTest.kt create mode 100644 app/src/androidTest/java/com/devmind/devoxx/MainActivityTest.kt create mode 100644 app/src/androidTest/java/com/devmind/devoxx/SpeakerListActivityTest.kt create mode 100644 app/src/androidTest/java/com/devmind/devoxx/SpeakerListViewModelTest.kt create mode 100644 app/src/androidTest/java/com/devmind/devoxx/SpeakerViewModelTest.kt create mode 100644 app/src/androidTest/java/com/devmind/devoxx/model/SpeakerDaoTest.kt diff --git a/app/src/androidTest/java/com/devmind/devoxx/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/devmind/devoxx/ExampleInstrumentedTest.kt deleted file mode 100644 index 552f4bf..0000000 --- a/app/src/androidTest/java/com/devmind/devoxx/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.devmind.devoxx - -import androidx.test.InstrumentationRegistry -import androidx.test.runner.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getTargetContext() - assertEquals("com.devmind.devoxx", appContext.packageName) - } -} diff --git a/app/src/androidTest/java/com/devmind/devoxx/MainActivityTest.kt b/app/src/androidTest/java/com/devmind/devoxx/MainActivityTest.kt new file mode 100644 index 0000000..d21b8d4 --- /dev/null +++ b/app/src/androidTest/java/com/devmind/devoxx/MainActivityTest.kt @@ -0,0 +1,24 @@ +package com.devmind.devoxx + +import androidx.test.espresso.Espresso +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.rule.ActivityTestRule +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +class MainActivityTest{ + + @get:Rule + val activityRule = ActivityTestRule(MainActivity::class.java) + + @Test + fun shouldDisplayTitle(){ + Espresso.onView(ViewMatchers.withId(R.id.title)) + .check(ViewAssertions.matches(ViewMatchers.withText(R.string.app_name))) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/devmind/devoxx/SpeakerListActivityTest.kt b/app/src/androidTest/java/com/devmind/devoxx/SpeakerListActivityTest.kt new file mode 100644 index 0000000..39e42b7 --- /dev/null +++ b/app/src/androidTest/java/com/devmind/devoxx/SpeakerListActivityTest.kt @@ -0,0 +1,76 @@ +package com.devmind.devoxx + +import androidx.arch.core.executor.testing.CountingTaskExecutorRule +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.contrib.RecyclerViewActions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ActivityTestRule +import com.devmind.devoxx.model.Speaker +import com.devmind.devoxx.model.SpeakerAdapter +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.TimeUnit + + +@RunWith(AndroidJUnit4ClassRunner::class) +class SpeakerListActivityTest { + + @get:Rule + val activityRule = ActivityTestRule(SpeakerListActivity::class.java, false, false) + + @get:Rule + val countingTaskExecutorRule = CountingTaskExecutorRule() + + constructor() { + val application = + InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as DevoxxApplication + application.deleteDatabase("devoxx2") + val dao = application.speakerDao() + if (dao.readAll().isEmpty()) { + dao.create(Speaker("Romain", "Guy")) + dao.create(Speaker("Chet", "Haase")) + } + } + + @Before + fun init() { + activityRule.launchActivity(null) + } + + @Test + fun shouldDisplaySpeakers() { + drain() + Espresso.onView(ViewMatchers.withText("Romain Guy")).check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + Espresso.onView(ViewMatchers.withText("Chet Haase")).check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + + } + + @Test + fun shouldCreateSpeaker() { + drain() + Espresso.onView(ViewMatchers.withId(R.id.buttonAddSpeaker)).perform(ViewActions.click()) + // After this click we should be on speaker view in creation mode + Espresso.onView(ViewMatchers.withId(R.id.speakerFirstname)).check(ViewAssertions.matches(ViewMatchers.withText(""))) + Espresso.onView(ViewMatchers.withId(R.id.speakerLastname)).check(ViewAssertions.matches(ViewMatchers.withText(""))) + } + + @Test + fun shouldUpdateSpeaker() { + drain() + Espresso.onView(ViewMatchers.withId(R.id.speakerList)).perform( + RecyclerViewActions.actionOnItemAtPosition(0, ViewActions.click())) + // After this click we should be on speaker view in update mode + Espresso.onView(ViewMatchers.withId(R.id.speakerFirstname)).check(ViewAssertions.matches(ViewMatchers.withText("Romain"))) + Espresso.onView(ViewMatchers.withId(R.id.speakerLastname)).check(ViewAssertions.matches(ViewMatchers.withText("Guy"))) + } + + fun drain() { + countingTaskExecutorRule.drainTasks(1, TimeUnit.MINUTES) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/devmind/devoxx/SpeakerListViewModelTest.kt b/app/src/androidTest/java/com/devmind/devoxx/SpeakerListViewModelTest.kt new file mode 100644 index 0000000..1cc290a --- /dev/null +++ b/app/src/androidTest/java/com/devmind/devoxx/SpeakerListViewModelTest.kt @@ -0,0 +1,60 @@ +package com.devmind.devoxx + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.devmind.devoxx.model.Speaker +import com.devmind.devoxx.model.SpeakerDao +import com.google.common.truth.Truth +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +class SpeakerListViewModelTest { + + /** + * Swaps background executor used by the Architecture Components with a different one which executes + * each task synchronously. + */ + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + lateinit var viewModel: SpeakerListViewModel + lateinit var speakerDao: SpeakerDao + lateinit var observer: Observer> + + + @Before + fun onInit() { + speakerDao = mockk(relaxUnitFun = true) + observer = mockk(relaxed = true) + + val devoxxApplication = mockk(){ + every { speakerDao() } returns speakerDao + } + viewModel = SpeakerListViewModel(devoxxApplication) + } + + @After + fun onTearDown(){ + viewModel.speakersLiveData.removeObserver(observer) + } + + @Test + fun shouldLoadSpeakers() { + val speakers = arrayListOf(Speaker("Romain", "Guy")) + every { speakerDao.readAll() } returns speakers + + viewModel.speakersLiveData.observeForever(observer) + + verify { speakerDao.readAll() } + verify { observer.onChanged(any()) } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/devmind/devoxx/SpeakerViewModelTest.kt b/app/src/androidTest/java/com/devmind/devoxx/SpeakerViewModelTest.kt new file mode 100644 index 0000000..48989be --- /dev/null +++ b/app/src/androidTest/java/com/devmind/devoxx/SpeakerViewModelTest.kt @@ -0,0 +1,104 @@ +package com.devmind.devoxx + +import androidx.arch.core.executor.testing.CountingTaskExecutorRule +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.lifecycle.Observer +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import com.devmind.devoxx.model.Speaker +import com.devmind.devoxx.model.SpeakerDao +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.TimeUnit + + +@RunWith(AndroidJUnit4ClassRunner::class) +class SpeakerViewModelTest { + + /** + * Swaps background executor used by the Architecture Components with a different one which executes + * each task synchronously. + */ + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + +// @get:Rule +// val countingTaskExecutorRule = CountingTaskExecutorRule() + + lateinit var viewModel: SpeakerViewModel + lateinit var speakerDao: SpeakerDao + lateinit var observer: Observer + + + @Before + fun onInit() { + speakerDao = mockk(relaxUnitFun = true) + observer = mockk(relaxed = true) + + val devoxxApplication = mockk(){ + every { speakerDao() } returns speakerDao + } + viewModel = SpeakerViewModel(devoxxApplication) + } + + @After + fun onTearDown(){ + viewModel.speakerLiveData.removeObserver(observer) + } + + @Test + fun shouldLoadSpeaker() { + val speaker = Speaker("Romain", "Guy") + + every { speakerDao.readOne(speaker.uuid) } returns speaker + + instantTaskExecutorRule.run { + viewModel.speakerLiveData.observeForever(observer) + viewModel.loadSpeaker(speaker.uuid) + + drain() + verify { speakerDao.readOne(speaker.uuid) } + verify { observer.onChanged(speaker) } + } + } + + @Test + fun shouldSaveSpeaker() { + val speaker = Speaker("Romain", "Guy") + + instantTaskExecutorRule.run { + viewModel.speakerLiveData.observeForever(observer) + viewModel.createSpeaker(speaker) + + drain() + verify { speakerDao.create(speaker) } + verify { observer.onChanged(speaker) } + } + } + + @Test + fun shouldUpdateSpeaker() { + val speaker = Speaker("Romain", "Guy") + + instantTaskExecutorRule.run { + viewModel.speakerLiveData.observeForever(observer) + viewModel.updateSpeaker(speaker) + + verify { speakerDao.update(speaker) } + verify { observer.onChanged(speaker) } + } + drain() +// verify { speakerDao.update(speaker) } +// verify { observer.onChanged(speaker) } + } + + fun drain(){ + instantTaskExecutorRule.run { } +// countingTaskExecutorRule.drainTasks(1, TimeUnit.MINUTES) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/devmind/devoxx/model/SpeakerDaoTest.kt b/app/src/androidTest/java/com/devmind/devoxx/model/SpeakerDaoTest.kt new file mode 100644 index 0000000..4c827f4 --- /dev/null +++ b/app/src/androidTest/java/com/devmind/devoxx/model/SpeakerDaoTest.kt @@ -0,0 +1,63 @@ +package com.devmind.devoxx.model + +import androidx.room.Room +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +class SpeakerDaoTest{ + + lateinit var database: DevoxxDatabase + lateinit var dao: SpeakerDao + private val speaker = Speaker("Chet", "Haase", "USA") + + @Before + fun onInit(){ + val testContext = InstrumentationRegistry.getInstrumentation().context + + database = Room.inMemoryDatabaseBuilder(testContext, DevoxxDatabase::class.java).allowMainThreadQueries().build() + dao = database.speakerDao() + dao.create(speaker) + } + + @After + fun onClose() = database.close() + + @Test + fun readAll(){ + val speakers = dao.readAll() + Truth.assertThat(speakers).containsExactly(speaker) + } + + @Test + fun readOne(){ + val speaker = dao.readOne(speaker.uuid) + Truth.assertThat(speaker).isEqualTo(speaker) + } + + @Test + fun readOneByUnknownId(){ + val speaker = dao.readOne("unknown") + Truth.assertThat(speaker).isNull() + } + + @Test + fun update(){ + dao.update(Speaker("Chet", "Guy", "USA", speaker.uuid)) + val speaker = dao.readOne(speaker.uuid) + Truth.assertThat(speaker.lastname).isEqualTo("Guy") + } + + @Test + fun delete(){ + dao.delete(speaker) + val speakers = dao.readAll() + Truth.assertThat(speakers).isEmpty() + } +} \ No newline at end of file