android espresso login once before running tests

12,359

Solution 1

Ideally you would test the login/logout functionality in a set of tests that just test different login/logout scenarios, and let the other tests focus on other use cases. However, since the other scenarios depend on the user being logged in, it sounds like one way to solve this would be to provide a mock version of the app component handling the login. For the other login dependent tests, you would inject this mock at the start and it would return mock user credentials that the rest of the app can work with.

Here's an example where Dagger, Mockito and Espresso is being used to accomplish this: https://engineering.circle.com/instrumentation-testing-with-dagger-mockito-and-espresso-f07b5f62a85b

Solution 2

I test an app that requires this same scenario. The easiest way I've gotten around this is to split up logging in and out into their own test classes. Then you add all your test classes to a suite, starting and ending with the login and logout suites respectively. Your test suites ends up looking kind of like this.

@RunWith(Suite.class)
@Suite.SuiteClasses({
        LoginSetup.class,
        SmokeTests.class,
        LogoutTearDown.class
})

EDIT: Here is an example of both the LoginSetup and LogoutTearDown tests. This solution really should only be for end-to-end tests and comprise a small portion of your testing efforts. fejd provides a solution for a full testing stack which also needs to be considered.

@LargeTest
public class SmokeSetup extends LogInTestFixture {

    @Rule
    public ActivityTestRule<LoginActivity> mLoginActivity = new ActivityTestRule<>(LoginActivity.class);

    @Test
    public void testSetup() throws IOException {

        onView(withId(R.id.username_field)).perform(replaceText("username"));
        onView(withId(R.id.password_field)).perform(replaceText("password"));
        onView(withId(R.id.login_button)).perform(click());

    }

}

@LargeTest
public class LogoutTearDown extends LogInTestFixture {

    @Rule
    public ActivityTestRule<MainActivity> mMainActivity = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testLogout() throws IOException {

        onView(withId(R.id.toolbar_menu)).perform(click());
        onView(withId(R.id.logout_button)).perform(click());

    }

}

Solution 3

The approach with logging in with @Before is nice but if your login is slow, your combined test time will be very slow.

Here's a great hack that works. The strategy is simple: run every test in order and fail every test before they get a chance to run if a certain test fails (in this case login test).

@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
public class YourTestsThatDependsOnLogin {

    private static failEverything;

    @Before
    public void beforeTest() {

        // Fail every test before it has a chance to run if login failed
        if (failEverything) {
            Assert.fail("Login failed so every test should fail");
        }

    }    

    @Test
    public void test0_REQUIREDTEST_login() {

        failEverything = true;
        // Your code for login
        // Your login method must fail the test if it fails.
        login();
        failEverything = false; // We are safe to continue.

    }

    // test1 test2 test3 etc...

}

Pros:

  • What you asked for works and it is fast (if your login is slow)
  • You can have multiple tests that depend on different logins, meaning you can do a bunch of tests for user1, then a bunch of tests for user2 etc.
  • Quick to set up.

Cons:

  • Not standard procedure and someone might wonder why so many tests fail...
  • You should mock users instead of actually logging in. You should test your login separately and tests should not depend on each other.

Solution 4

Add the following function in your test file, replace the code in the try block with yours that performs the login actions.

@Before
fun setUp() {
    // Login if it is on the LoginActivity
    try {
        // Type email and password
        Espresso.onView(ViewMatchers.withId(R.id.et_email))
                .perform(ViewActions.typeText("a_test_account_username"), ViewActions.closeSoftKeyboard())
        Espresso.onView(ViewMatchers.withId(R.id.et_password))
                .perform(ViewActions.typeText("a_test_account_password"), ViewActions.closeSoftKeyboard())

        // Click login button
        Espresso.onView(ViewMatchers.withId(R.id.btn_login)).perform(ViewActions.click())
    } catch (e: NoMatchingViewException) {
        //view not displayed logic
    }
}

With this @Before annotation, this setUp function will be executed before any other tests you have in this test file. If the app lands on Login Activity, do the login in this setUp function. The example here assumes there is an EditText for email and password, as well as a login button. It uses Expresso to type the email and password, then hit the login button. The try catch block is to ensure if you are not landing on the Login Activity, it will catch the error and do nothing, and if you didn't land on the Login Activity, then you are good to go on other tests anyway.

Note: this is Kotlin code, but it looks very similar to Java.

Solution 5

My Application also requires the user to be logged in through-out the test run. However, I am able to login the first time and the application remembers my username/password throughout the test run. In fact, it remembers the credentials until I force it to forget them or uninstall and install the app again.

During a test run, after every test, my app goes to the background and is resumed again at the beginning of the next test. I am guessing your application requires a user to enter their credentials every time you bring it to the front from the background (banking application maybe?). Is there a setting in your application that will "Remember your credentials"? If yes, you can easily enable it right after you login for the first time in your test run.

Other than that, I think you should talk to the developers about providing you a way to remember your credentials.

Share:
12,359
source.rar
Author by

source.rar

Updated on June 11, 2022

Comments

  • source.rar
    source.rar about 2 years

    I've been trying to cover my Android app with tests and have started using espresso recently. Pretty impressed with it so far. However most of my app's functionality requires that users are logged in. And since all tests are independent, this requires registering a new user for each test. This works fine however the time required for each test increases considerably because of this.

    I am trying to find a way to register a user once in a class (of tests) and then use that same user account to perform all the tests in that class.

    One way I have been able to do this is to actually have only one test (@Test) method that runs all the other tests in the order I want. However this is an all or nothing approach, since the gradle cAT task only outputs the results once at the end without providing info about the intermediate tests that may have passed/failed.

    I also tried the @BeforeClass approach which however did not work (no gradle output from the class where I had used this even with the debug option and it seemed like it took a long time before it moved on to the next class of tests).

    Is there a better approach to register a user once at start of a class and then logout once at the end of testing?

    Any help appreciated.