From b7514e0e82e743329023553377d5f4cbd0cdf103 Mon Sep 17 00:00:00 2001 From: Shubham Singh Date: Tue, 11 Jan 2022 18:05:41 +0530 Subject: [PATCH 1/2] Created UI tests for PraxisNavigation flow, AuthenticationUI Screen, and ForgotPasswordUI Screen --- app/build.gradle.kts | 2 +- .../com/mutualmobile/praxis/base/BaseTest.kt | 15 +++++++ .../ui/AuthenticationUIShould.kt | 38 ++++++++++++++++ .../praxis/root/PraxisNavigationShould.kt | 44 +++++++++++++++++++ app/src/main/AndroidManifest.xml | 1 - .../authentication/ui/AuthenticationUI.kt | 4 +- .../authentication/ui/ForgotPasswordUI.kt | 2 +- 7 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt create mode 100644 app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt create mode 100644 app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 73c72f74..afd1b216 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,7 +26,7 @@ android { targetSdk = (ProjectProperties.TARGET_SDK) versionCode = 1 versionName = "1.0" - testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true } diff --git a/app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt b/app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt new file mode 100644 index 00000000..0d8b103c --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt @@ -0,0 +1,15 @@ +package com.mutualmobile.praxis.base + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.mutualmobile.praxis.root.MainActivity +import org.junit.Rule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +abstract class BaseComposeTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt b/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt new file mode 100644 index 00000000..27a85f6f --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt @@ -0,0 +1,38 @@ +package com.mutualmobile.praxis.feat.authentication.ui + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import com.mutualmobile.praxis.base.BaseComposeTest +import org.junit.Test + +class AuthenticationUIShould: BaseComposeTest() { + + @Test + fun display_emailErrorSnackBar_onClick_of_loginBtn_with_empty_credentials() { + with(composeTestRule) { + onNodeWithText("Login").performClick() + onNodeWithText("Email is not valid").assertIsDisplayed() + } + } + + @Test + fun display_passwordErrorSnackBar_onClick_of_loginBtn_with_empty_password_only() { + with(composeTestRule) { + onNodeWithText("Email").performTextInput("shubham@gmail.com") + onNodeWithText("Login").performClick() + onNodeWithText("Password should be at least 6 characters long").assertIsDisplayed() + } + } + + @Test + fun display_emailErrorSnackBar_onClick_of_loginBtn_with_empty_email_only() { + with(composeTestRule) { + onNodeWithText("Password").performTextInput("testpassword") + onNodeWithText("Login").performClick() + onNodeWithText("Email is not valid").assertIsDisplayed() + } + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt b/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt new file mode 100644 index 00000000..830145d9 --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt @@ -0,0 +1,44 @@ +package com.mutualmobile.praxis.root + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import com.mutualmobile.praxis.base.BaseComposeTest +import org.junit.Test + +class PraxisNavigationShould : BaseComposeTest() { + + @Test + fun check_if_all_items_are_displayed_correctly() { + with(composeTestRule) { + onNodeWithText("Authentication").assertIsDisplayed() + onNodeWithContentDescription("Logo").assertIsDisplayed() + onNodeWithText("Email").assertIsDisplayed() + onNodeWithContentDescription("Email").assertIsDisplayed() + onNodeWithText("Password").assertIsDisplayed() + onNodeWithContentDescription("Password").assertIsDisplayed() + onNodeWithText("Login").assertIsDisplayed() + onNodeWithText("Forgot Password?").assertIsDisplayed() + } + } + + @Test + fun navigate_to_forgotPasswordScreen_onClick_of_forgotPassword_text() { + with(composeTestRule) { + onNodeWithText("Forgot Password?").performClick() + onNodeWithText("Forgot Password").assertIsDisplayed() + } + } + + @Test + fun navigate_to_dashboard_on_correct_credentials() { + with(composeTestRule) { + onNodeWithText("Email").performTextInput("shubham@gmail.com") + onNodeWithText("Password").performTextInput("testpassword") + onNodeWithText("Login").performClick() + onNodeWithText("Chuck Norris Random Joke Generator").assertIsDisplayed() + } + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6009593c..44dc5be6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,6 @@ diff --git a/featauthentication/src/main/java/com/praxis/feat/authentication/ui/AuthenticationUI.kt b/featauthentication/src/main/java/com/praxis/feat/authentication/ui/AuthenticationUI.kt index 5707cc3a..50760014 100644 --- a/featauthentication/src/main/java/com/praxis/feat/authentication/ui/AuthenticationUI.kt +++ b/featauthentication/src/main/java/com/praxis/feat/authentication/ui/AuthenticationUI.kt @@ -105,7 +105,7 @@ fun ForgotPasswordText(authVM: AuthVM) { color = PraxisTheme.colors.accent, ) ) { - append("Forgot Password? ") + append("Forgot Password?") } }, onClick = { @@ -149,7 +149,7 @@ private fun PasswordTF(authVM: AuthVM) { leadingIcon = { Image( painter = painterResource(id = R.drawable.ic_eye), - contentDescription = "email" + contentDescription = "Password" ) }, colors = textFieldColors(), diff --git a/featauthentication/src/main/java/com/praxis/feat/authentication/ui/ForgotPasswordUI.kt b/featauthentication/src/main/java/com/praxis/feat/authentication/ui/ForgotPasswordUI.kt index 9e490809..a5c9bfba 100644 --- a/featauthentication/src/main/java/com/praxis/feat/authentication/ui/ForgotPasswordUI.kt +++ b/featauthentication/src/main/java/com/praxis/feat/authentication/ui/ForgotPasswordUI.kt @@ -29,7 +29,7 @@ fun ForgotPasswordUI(forgotPasswordVM: ForgotPasswordVM = hiltViewModel()){ .statusBarsPadding() .navigationBarsPadding(), topBar = { - CommonTopAppBar(titleText = "ForgotPasswordentication") + CommonTopAppBar(titleText = "Forgot Password") }) { ForgotPasswordSurface(forgotPasswordVM) } From d62769777346a29836598a460aabb072562b4783 Mon Sep 17 00:00:00 2001 From: Shubham Singh Date: Wed, 12 Jan 2022 17:23:49 +0530 Subject: [PATCH 2/2] Implemented MockWebServer response based tests for Dashboard and JokeDetail screens --- app/build.gradle.kts | 7 +- .../com/mutualmobile/base/BaseComposeTest.kt | 36 ++++++++++ .../base/TestPraxisApplication.kt | 20 ++++++ .../com/mutualmobile/di/FakeNetworkModule.kt | 65 +++++++++++++++++++ .../feat/jokes/ui/home/DashboardShould.kt | 42 ++++++++++++ .../com/mutualmobile/praxis/base/BaseTest.kt | 15 ----- .../ui/AuthenticationUIShould.kt | 4 +- .../praxis/root/PraxisNavigationShould.kt | 4 +- .../com/mutualmobile/utils/NetworkUtils.kt | 20 ++++++ .../resources/responses/jokes_response.json | 30 +++++++++ app/src/main/AndroidManifest.xml | 4 +- 11 files changed, 228 insertions(+), 19 deletions(-) create mode 100644 app/src/androidTest/java/com/mutualmobile/base/BaseComposeTest.kt create mode 100644 app/src/androidTest/java/com/mutualmobile/base/TestPraxisApplication.kt create mode 100644 app/src/androidTest/java/com/mutualmobile/di/FakeNetworkModule.kt create mode 100644 app/src/androidTest/java/com/mutualmobile/feat/jokes/ui/home/DashboardShould.kt delete mode 100644 app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt create mode 100644 app/src/androidTest/java/com/mutualmobile/utils/NetworkUtils.kt create mode 100644 app/src/androidTest/resources/responses/jokes_response.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index afd1b216..ebeff447 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,7 +26,7 @@ android { targetSdk = (ProjectProperties.TARGET_SDK) versionCode = 1 versionName = "1.0" - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "com.mutualmobile.base.TextPraxisApplication" vectorDrawables.useSupportLibrary = true } @@ -105,7 +105,12 @@ dependencies { testImplementation(TestLib.ROBO_ELECTRIC) testImplementation(TestLib.COROUTINES) testImplementation(TestLib.MOCKK) + androidTestImplementation(TestLib.MOCKK) + androidTestImplementation(TestLib.MOCK_WEB_SERVER) androidTestImplementation("androidx.compose.ui:ui-test-junit4:${Lib.Android.COMPOSE_VERSION}") debugImplementation("androidx.compose.ui:ui-test-manifest:${Lib.Android.COMPOSE_VERSION}") + androidTestImplementation("com.google.dagger:hilt-android-testing:2.38.1") + kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.38.1") + androidTestImplementation("androidx.test:runner:1.4.0") } diff --git a/app/src/androidTest/java/com/mutualmobile/base/BaseComposeTest.kt b/app/src/androidTest/java/com/mutualmobile/base/BaseComposeTest.kt new file mode 100644 index 00000000..ae621dee --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/base/BaseComposeTest.kt @@ -0,0 +1,36 @@ +package com.mutualmobile.base + +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.mutualmobile.praxis.root.MainActivity +import dagger.hilt.android.testing.HiltAndroidRule +import javax.inject.Inject +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +abstract class BaseComposeTest { + + @get:Rule + val composeTestRule = createAndroidComposeRule() + + @get:Rule + var hiltRule = HiltAndroidRule(this) + + @Inject + lateinit var mockWebServer: MockWebServer + + @Before + fun setup() { + hiltRule.inject() + } + + @After + fun tearDown() { + mockWebServer.shutdown() + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/base/TestPraxisApplication.kt b/app/src/androidTest/java/com/mutualmobile/base/TestPraxisApplication.kt new file mode 100644 index 00000000..23acefbb --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/base/TestPraxisApplication.kt @@ -0,0 +1,20 @@ +package com.mutualmobile.base + +import android.app.Application +import android.content.Context +import android.os.Bundle +import android.os.StrictMode +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class TextPraxisApplication : AndroidJUnitRunner() { + + override fun onCreate(arguments: Bundle?) { + StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().permitAll().build()) + super.onCreate(arguments) + } + + override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/di/FakeNetworkModule.kt b/app/src/androidTest/java/com/mutualmobile/di/FakeNetworkModule.kt new file mode 100644 index 00000000..4b54b5bd --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/di/FakeNetworkModule.kt @@ -0,0 +1,65 @@ +package com.mutualmobile.di + +import com.mutualmobile.praxis.data.injection.NetworkModule +import com.mutualmobile.praxis.data.remote.JokeApiService +import com.mutualmobile.praxis.data.remote.RetrofitHelper +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import javax.inject.Named +import javax.inject.Singleton +import okhttp3.OkHttpClient +import okhttp3.mockwebserver.MockWebServer +import retrofit2.Retrofit + +@Module +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [NetworkModule::class] +) +class TestNetworkModule { + + @Provides + @Singleton + fun provideMockWebServer(): MockWebServer { + var mockWebServer: MockWebServer? = null + val thread = Thread { + mockWebServer = MockWebServer() + mockWebServer?.start() + } + thread.start() + thread.join() + return mockWebServer!! + } + + @Provides + @Singleton + fun provideBaseUrl(mockWebServer: MockWebServer): String { + return mockWebServer.url("/") + .toString() + } + + @Provides + @Singleton + fun provideHttpClient(): OkHttpClient { + return RetrofitHelper.createOkHttpClient() + } + + @Provides + @Singleton + fun provideRetrofit( + okHttpClient: OkHttpClient, + rootUrl: String + ): Retrofit { + return RetrofitHelper.createRetrofitClient(okHttpClient, rootUrl) + } + + @Provides + @Singleton + fun provideJokesApiService(retrofit: Retrofit): JokeApiService { + return JokeApiService.createRetrofitService(retrofit) + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/feat/jokes/ui/home/DashboardShould.kt b/app/src/androidTest/java/com/mutualmobile/feat/jokes/ui/home/DashboardShould.kt new file mode 100644 index 00000000..d2024574 --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/feat/jokes/ui/home/DashboardShould.kt @@ -0,0 +1,42 @@ +package com.mutualmobile.feat.jokes.ui.home + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import com.mutualmobile.base.BaseComposeTest +import com.mutualmobile.praxis.commonui.theme.PraxisTheme +import com.mutualmobile.utils.enqueueResponse +import dagger.hilt.android.testing.HiltAndroidTest +import org.junit.Test + +@HiltAndroidTest +class DashboardShould : BaseComposeTest() { + + @Test + fun display_demo_jokes() { + mockWebServer.enqueueResponse("jokes_response.json", 200) + composeTestRule.setContent { + PraxisTheme { + Dashboard() + } + } + with(composeTestRule) { + onNodeWithText("Chuck Norris can believe it's not butter.").assertIsDisplayed() + } + } + + @Test + fun display_jokeDetailScreen_onClick_of_joke() { + with(composeTestRule) { + onNodeWithText("Email").performTextInput("shubham@gmail.com") + onNodeWithText("Password").performTextInput("testpassword") + onNodeWithText("Login").performClick() + onNodeWithText("Chuck Norris Random Joke Generator").assertIsDisplayed() + mockWebServer.enqueueResponse("jokes_response.json", 200) + onNodeWithText("Chuck Norris can believe it's not butter.").performClick() + onNodeWithText("Joke Detail").assertIsDisplayed() + } + } + +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt b/app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt deleted file mode 100644 index 0d8b103c..00000000 --- a/app/src/androidTest/java/com/mutualmobile/praxis/base/BaseTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.mutualmobile.praxis.base - -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.mutualmobile.praxis.root.MainActivity -import org.junit.Rule -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -abstract class BaseComposeTest { - - @get:Rule - val composeTestRule = createAndroidComposeRule() - -} \ No newline at end of file diff --git a/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt b/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt index 27a85f6f..0672863d 100644 --- a/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt +++ b/app/src/androidTest/java/com/mutualmobile/praxis/feat/authentication/ui/AuthenticationUIShould.kt @@ -4,9 +4,11 @@ import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput -import com.mutualmobile.praxis.base.BaseComposeTest +import com.mutualmobile.base.BaseComposeTest +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class AuthenticationUIShould: BaseComposeTest() { @Test diff --git a/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt b/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt index 830145d9..3f9c5cca 100644 --- a/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt +++ b/app/src/androidTest/java/com/mutualmobile/praxis/root/PraxisNavigationShould.kt @@ -5,9 +5,11 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput -import com.mutualmobile.praxis.base.BaseComposeTest +import com.mutualmobile.base.BaseComposeTest +import dagger.hilt.android.testing.HiltAndroidTest import org.junit.Test +@HiltAndroidTest class PraxisNavigationShould : BaseComposeTest() { @Test diff --git a/app/src/androidTest/java/com/mutualmobile/utils/NetworkUtils.kt b/app/src/androidTest/java/com/mutualmobile/utils/NetworkUtils.kt new file mode 100644 index 00000000..d67fa114 --- /dev/null +++ b/app/src/androidTest/java/com/mutualmobile/utils/NetworkUtils.kt @@ -0,0 +1,20 @@ +package com.mutualmobile.utils + +import java.nio.charset.StandardCharsets +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okio.buffer +import okio.source + +internal fun MockWebServer.enqueueResponse(fileName: String, code: Int) { + val inputStream = javaClass.classLoader?.getResourceAsStream("responses/$fileName") + + val source = inputStream?.let { inputStream.source().buffer() } + source?.let { + enqueue( + MockResponse() + .setResponseCode(code) + .setBody(source.readString(StandardCharsets.UTF_8)) + ) + } +} \ No newline at end of file diff --git a/app/src/androidTest/resources/responses/jokes_response.json b/app/src/androidTest/resources/responses/jokes_response.json new file mode 100644 index 00000000..695c25cb --- /dev/null +++ b/app/src/androidTest/resources/responses/jokes_response.json @@ -0,0 +1,30 @@ +{ + "type": "success", + "value": [ + { + "id": 427, + "joke": "Chuck Norris' favorite cereal is Kellogg's Nails 'N' Gravel.", + "categories": [] + }, + { + "id": 75, + "joke": "Chuck Norris can believe it's not butter.", + "categories": [] + }, + { + "id": 302, + "joke": "Chuck Norris doesn't go on the internet, he has every internet site stored in his memory. He refreshes webpages by blinking.", + "categories": [] + }, + { + "id": 275, + "joke": "Little Miss Muffet sat on her tuffet, until Chuck Norris roundhouse kicked her into a glacier.", + "categories": [] + }, + { + "id": 76, + "joke": "If tapped, a Chuck Norris roundhouse kick could power the country of Australia for 44 minutes.", + "categories": [] + } + ] +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 44dc5be6..7217ff04 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,13 +6,15 @@ + tools:ignore="GoogleAppIndexingWarning" + tools:targetApi="m">