4

I'm creating UI Tests for my Android app using Espresso.

I have a login screen which contains just two Fields (user and password) and a Button.

I'm using Retrofit but I want to mock the API response so I've created a RetrofitMockClient.

For example, in the next Test I type a wrong password intentionally to check that an error message is displayed.

@Test
public void loginFailed() {
    onView(withId(R.id.login_user_name)).perform(typeText("myUser"));
    onView(withId(R.id.login_user_password)).perform(typeText("wrongPassword"), closeSoftKeyboard());

    onView(withId(R.id.login_button)).perform(click());

    // Failed login shows error message.
    onView(withText(R.string.login_error_message)).check(matches(isDisplayed()));
}

The problem is that when the click is performed, that triggers a Retrofit API call, the one I want to mock.

Any ideas about it? I'm also a bit confused about UI Test / Unit Tests and if this Test makes sense or not. I just want to check that the error message is displayed when the API returns an error during the login process.

Thanks in advance.

Edit:

I've introduced MockWebServer as the user fzxt suggested, using the RestServiceTestHelper class provided in the example.

Now my Test looks like this:

@RunWith(AndroidJUnit4.class)
public class LoginUIAndroidTest {

    private static MockWebServer mockWebServer;


    @Rule
    public ActivityTestRule<LoginActivity> mActivityRule = new IntentsTestRule<>(LoginActivity.class,
            true,    // initialTouchMode
            false); // launchActivity - false so we could customize the intent




    @BeforeClass
    public static void startMockServer() throws Exception {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
        mockWebServer.url("/");
    }


    @AfterClass
    public static void shutdownMockServer() throws Exception {
        mockWebServer.shutdown();
    }
    @Test
    public void testLoginSuccess() throws Exception {

        String fileName = "quote_login_200_ok.json";
        mockWebServer.enqueue(new MockResponse()
                .setResponseCode(200)
                .setBody(RestServiceTestHelper.getStringFromFile(getInstrumentation().getContext(), fileName)));


        onView(withId(R.id.login_user_name)).perform(typeText("user"));
        onView(withId(R.id.login_user_password)).perform(typeText("password"), closeSoftKeyboard());

        onView(withId(R.id.login_button)).perform(click());

        // Successful login moves user to next screen.
        intended(hasComponent(new ComponentName(getInstrumentation().getTargetContext(), ProjekteActivity.class)));
    }

RestServiceTestHelper.java:

public class RestServiceTestHelper {

    public static String convertStreamToString(InputStream is) throws Exception {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
        }
        reader.close();
        return sb.toString();
    }

    public static String getStringFromFile(Context context, String filePath) throws Exception {
        final InputStream stream = context.getResources().getAssets().open(filePath);

        String ret = convertStreamToString(stream);
        //Make sure you close all streams.
        stream.close();
        return ret;
    }
}

Here is my LoginActivity:

 public class LoginActivity extends Activity {

        ...

        @OnClick(R.id.login_button)
        public void loginUser(final View view) {
            final MyRetrofitService service = createRetrofitService();

            service.login(new AuthenticationLoginDTO(email, password, deviceUUID), new Callback < AuthenticationDTO > () {
                @Override
                public void success(final AuthenticationDTO authenticationDTO, final Response response) {}

                @Override
                public void failure(final RetrofitError retrofitError) {}
            });

        }



        protected MyRetrofitService createRetrofitService() {
            final RequestInterceptor requestInterceptor = new RequestInterceptor() {
                @Override
                public void intercept(final RequestInterceptor.RequestFacade request) {
                    request.addHeader(getString(R.string.header_api_key), getString(R.string.api_key));
                    request.addHeader(getString(R.string.header_context), getString(R.string.context));
                }
            };
            final Gson dateTimeConverter = new GsonBuilder()
                .registerTypeAdapter(DateTime.class, new DateTimeConverter())
                .create();
            final RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint("/")
                .setConverter(new GsonConverter(dateTimeConverter))
                .setRequestInterceptor(requestInterceptor)
                .setExecutors(AsyncTask.THREAD_POOL_EXECUTOR, new MainThreadExecutor())
                .setLogLevel(RestAdapter.LogLevel.FULL).setLog(new AndroidLog(Config.TAG))
                .build();
            return restAdapter.create(MyRetrofitService.class);
        }
    }

And the Retrofit Interface:

public interface NGOPService {

    String AUTHENTICATION_ENDPOINT = "";

    @POST(AUTHENTICATION_ENDPOINT)
    void login(@Body AuthenticationLoginDTO authRequest, Callback < AuthenticationDTO > callback);

}

I'm missing something because when Espresso performs the click action, the real Api is being called instead of MockWebServer.

onView(withId(R.id.login_button)).perform(click());

I've seen some examples using Dagger 2 and Retrofit 2.0 but unfortunately, I have to use Retrofit 1.9 for now.

Thanks in advance.

Kohei TAMURA
  • 4,970
  • 7
  • 25
  • 49
Ale
  • 2,282
  • 5
  • 38
  • 67
  • 1
    You can try [MockWebserver](https://github.com/square/okhttp/tree/master/mockwebserver). [An example](https://github.com/riggaroo/android-retrofit-test-examples/blob/master/RetrofitTestExample/app/src/androidTest/java/za/co/riggaroo/retrofittestexample/MainActivityTest.java) –  Jul 04 '17 at 13:47
  • thanks for your answer @fzxt, I've updated my question – Ale Jul 06 '17 at 11:01
  • 1
    In your activity, have you set it so that it actually sends the request to "/"? If it's just pinging the actual server than your MockWebServer will never be used. –  Jul 07 '17 at 23:41
  • do you mean this line? `mockWebServer.url("/");` – Ale Jul 10 '17 at 06:57
  • 1
    I mean your actual activity code. What's happening here currently is that you start a webserver, type in user, password and click login, which will call your loginactivitys onClick listener for that button. You can go about it two ways off the top of my head, one you can change your loginactivity onclick listener so that it sends the request to the mockwebserver. Two, you can use [mockito](http://site.mockito.org/) and use [when/then](http://www.baeldung.com/mockito-behavior) so that when your loginactivity calls the method to ping your webserver, you can mock the response. –  Jul 10 '17 at 13:28
  • thanks again @fzxt, I'm sorry to bother you but I have no experience on this. I've changed the retrofit endpoint to `/` but still the mockwebserver is not being called. I'm a bit confused when you say ` one you can change your loginactivity onclick listener so that it sends the request to the mockwebserver`. Ok it makes sense but how? the only way I have to do that is setting the retrofit url to `/`, right? I'm gonna edit my question to show the retrofit call. Thanks a lot for your time – Ale Jul 11 '17 at 08:57

0 Answers0