在为 Espresso 测试创建 activity 之前更改共享首选项

Altering shared preferences before creating activity for Espresso testing

我正在尝试为登录页面创建 Espresso 测试,但我 运行 遇到一个问题,一旦涉及成功登录的测试运行,任何后续测试现在都出错 activity 和重新 运行 登录测试需要完全重新启动虚拟设备以擦除共享首选项(这是因为登录 activity 检查所述首选项以查看是否已成功登录并跳过到应用程序主页面)。

我的问题是,一旦创建了 ActivityScenarioRule,更改共享首选项已经为时已晚,因为 activity 已经更改为主页。这是我的代码:

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 = "test2@l.com";
    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();
    }

    }

我认为您的问题在于使用 ActivityScenarioRule,我认为它会在您声明后立即为您启动 Activity,我总是发现它有问题,因此不使用该规则。

我想你可以尝试两件事:

1 - 将您的首选项设置逻辑提取到规则中并确保它首先被调用:

// 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))

并更新 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 - 将 ActivityScenarioRule 替换为手动管理的方案

只需手动启动和控制场景,也许在您的设置中有类似的行为:

// 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.launch(LoginActivity.class);
}

@After
public void tearDown() throws Exception {
    Intents.release();
    scenario.close();
}