I'm trying to create Espresso tests for a login page but I'm running into an issue where once a test involving a successful login runs, any subsequent tests are now in a wrong activity and re-running the login tests requires entirely restarting the virtual device so as to wipe the shared preferences (this is because the loginactivity checks said preferences to see if a successful login has occurred and skips to the main app page).
My issue is that once the ActivityScenarioRule is created, changing the shared preferences is already too late as the activity will already have changed to the main page. Here is my code:
package com.androidapp.zitgenius;
import static androidx.test.espresso.Espresso.onView;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import androidx.test.espresso.intent.Intents;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
/**
* Instrumented test of Login Activity, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class LoginActivityTest{
public final String INCORRECT_USERNAME = "this isn't a real username";
public final String INCORRECT_PASSWORD = "this isn't a real password";
public final String CORRECT_USERNAME = "[email protected]";
public final String CORRECT_PASSWORD = "123";
private static final String SHARE_PREF_NAME ="login_ref" ;
private static final String KEY_USER_ID ="id" ;
public void setSharedPrefs(){
SharedPreferences sharedPreferences = getInstrumentation().getContext().getSharedPreferences(SHARE_PREF_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit() ;
editor.putInt(KEY_USER_ID, -1) ;
editor.apply(); //this will prevent automatic logins from disrupting tests
Log.i("TESTING","Setting up shared prefs");
}
@Rule
public ActivityScenarioRule<LoginActivity> activityScenarioRule
= new ActivityScenarioRule<>(LoginActivity.class);
private View decorView;
@Before
public void setUp() {
Intents.init();
setSharedPrefs();
activityScenarioRule
= new ActivityScenarioRule<>(LoginActivity.class);
}
@Test
public void inputLoginJustUsername() {
SharedPreferences sharedPreferences = getInstrumentation().getContext().getSharedPreferences(SHARE_PREF_NAME, Context.MODE_PRIVATE);
Log.i("TESTING",String.valueOf(sharedPreferences.getInt(KEY_USER_ID, -1)));
//Type text and then hit Login Button
onView(withId(R.id.editText_username_input)).perform(typeText(CORRECT_USERNAME), closeSoftKeyboard());
onView(withId(R.id.button_login)).perform(click());
// Confirm inputted text remains
onView(withId(R.id.editText_username_input)).check(matches(withText(CORRECT_USERNAME)));
//TODO: Confirm Toast error message
}
@Test
public void inputLoginJustPassword() {
//Type text and then hit Login Button
onView(withId(R.id.editText_password_input)).perform(typeText(CORRECT_PASSWORD), closeSoftKeyboard());
onView(withId(R.id.button_login)).perform(click());
// Confirm inputted text remains
onView(withId(R.id.editText_password_input)).check(matches(withText(CORRECT_PASSWORD)));
//TODO: Confirm Toast error message
}
@Test
public void inputLoginIncorrectCredentials() {
//Type text and then hit Login Button
onView(withId(R.id.editText_username_input)).perform(typeText(INCORRECT_USERNAME), closeSoftKeyboard());
onView(withId(R.id.editText_password_input)).perform(typeText(INCORRECT_PASSWORD), closeSoftKeyboard());
onView(withId(R.id.button_login)).perform(click());
// Confirm inputted text remains
onView(withId(R.id.editText_username_input)).check(matches(withText(INCORRECT_USERNAME)));
onView(withId(R.id.editText_password_input)).check(matches(withText(INCORRECT_PASSWORD)));
//TODO: Confirm Toast error message
}
@Test
public void inputLoginCorrectCredentials() {
Log.i("TESTING", "Starting correct cred test");
int totalMillisToWait = 3000;
int pollEveryMillis = 300;
//Type text and then hit Login Button
onView(withId(R.id.editText_username_input)).perform(typeText(CORRECT_USERNAME), closeSoftKeyboard());
onView(withId(R.id.editText_password_input)).perform(typeText(CORRECT_PASSWORD), closeSoftKeyboard());
onView(withId(R.id.button_login)).perform(click());
//Wait for response
for(int i = 0; i < totalMillisToWait/pollEveryMillis; i ){
try {
Thread.sleep((long)pollEveryMillis);
} catch(InterruptedException e) {
System.out.println("got interrupted!");
}
}
// Confirm transition to NavBarActivity
Log.i("TESTING", "Checking intents");
intended(hasComponent(BottomNavigationActivity.class.getName()));
}
@Test
public void switchToSignupActivity() {
//Hit button to switch to signup
onView(withId(R.id.button_switch_signup)).perform(click());
// Confirm transition to Signup Activity
intended(hasComponent(SignupActivity.class.getName()));
}
@After
public void tearDown() throws Exception {
Intents.release();
}
}
CodePudding user response:
I believe your issue is with using ActivityScenarioRule
which I think launches an Activity for you as soon as your declare it, which I always found problematic and thus do not use that rule.
I think you could try two things:
1 - Extract your preference setting logic into a rule and ensure it gets called first:
// Here SharedPreferencesRule is a custom rule based on your "setSharedPrefs" function
// I'll leave that as an exercise to the reader
@Rule
public RuleChain rules = RuleChain.outer(new SharedPreferencesRule())
.around(new ActivityScenarioRule<>(LoginActivity.class))
And update logic in Before
:
@Before
public void setUp() {
Intents.init();
// setSharedPrefs(); // This would no longer exist, done by the rule
// activityScenarioRule // Delete this
// = new ActivityScenarioRule<>(LoginActivity.class);
}
2 - Replace ActivityScenarioRule
with manually-managed scenario
Just manually launch and control the scenario, maybe in your setup for similar behavior:
// Remove this rule
// @Rule
// public ActivityScenarioRule<LoginActivity> activityScenarioRule
// = new ActivityScenarioRule<>(LoginActivity.class);
private ActivityScenario<LoginActivity> scenario = null;
private View decorView;
@Before
public void setUp() {
Intents.init();
setSharedPrefs(); // Set prefs before launching activity
scenario = ActivityScenario<LoginActivity>.launch(LoginActivity.class);
}
@After
public void tearDown() throws Exception {
Intents.release();
scenario.close();
}