Home > front end >  Altering shared preferences before creating activity for Espresso testing
Altering shared preferences before creating activity for Espresso testing

Time:11-02

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();
}
  • Related