Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nullifying or overriding API calls made in an Activity under an Espresso test

I have an Activity which performs an API call during it's onCreate() lifecycle method. If this call fails (which it will usually fail in a debug or test environment), a ViewStub is inflated which acts as an error screen. This ViewStub covers the rest of the Activity.

This causes problems while performing UI tests with Espresso. I'd like the ability to either nullify or control the outcome of this request, so that I can write predictable tests for it.

Below is the API code in the Activity:

@Override                                                                                             
protected void onCreate(@Nullable Bundle savedInstanceState) {                                        

    // ...                                                  

    // Perform call                                                                                   
    viewModel.loadStuff()                                                                           
        .subscribeOn(Schedulers.io())                                                                 
        .observeOn(AndroidSchedulers.mainThread())                                                    
        .doOnSubscribe(disposable -> progressBar.setVisibility(View.VISIBLE))                         
        .doOnComplete(() -> progressBar.setVisibility(View.GONE))                                     
        .subscribe(response -> {                                              
            // Success
            // ...                                                                                         
        }, throwable -> {
            // Fail                                                                
            throwable.printStackTrace();                                                          
            errorStub.inflate();                                                                                          
        });
}

One of the Espresso tests I was writing was to test the enabled state of a button in this Activity after a few seconds. However, due to the above API call failing when under test, the button is covered by the ViewStub and the test fails.

I've tried implementing OkHTTP3's MockWebServer (note that the application uses Retrofit, not OkHTTP directly) by scripting a custom success response. However, it appears that to use MockWebServer you have to use the custom URL that it returns, and have your application code use that URL for it's calls. This means modifying application code to accommodate test code, which does not make sense.

I've also heard of custom Dagger setups to work around this problem.

Questions:

  1. How can I setup my test so that the API call performed in the Activity's onCreate method can be nullified or controlled?
  2. If MockWebServer is the correct thing to use for this case, how can I use it without modifying application code?

General comments and tips related to this question are appreciated.

like image 413
Orbit Avatar asked Apr 17 '18 18:04

Orbit


1 Answers

How can I setup my test so that the API call performed in the Activity's onCreate method can be nullified or controlled?

It seems your viewModel.loadStuff() is hardwired to some pre-defiend URL. To make it testable I would pass the url as parameter during viewmodel creation or pass it to the loadStuff method. I'll demonstrate the second option.
The next question is how can we change the url during testing? One way to do it is to store the url in Application instance and override the value for testing.

1. Create the application class which holds the url value.

class MyApplication extends Application {
    private static final String DEFAULT_URL = "http://yourdomain.com/api";
    private String url;

    @Override
    void onCreate() {
        super.onCreate();

        url = DEFAULT_URL;
    }

    public String getUrl() {
        return url;
    }

    @VisibleForTesting
    public void setUrl(String newUrl) {
        url = newUrl
    }
}

2. Resolve the url value from your activity

@Override                                                                                             
protected void onCreate(@Nullable Bundle savedInstanceState) {                                        
    // resolve url value
    String url = ((MyApplication) getApplication()).getUrl();                                         

    // Perform call                                                                                   
    viewModel.loadStuff(url)                                                                           
        .subscribeOn(Schedulers.io())                                                                 
        .observeOn(AndroidSchedulers.mainThread())                                                    
        .doOnSubscribe(disposable -> progressBar.setVisibility(View.VISIBLE))                         
        .doOnComplete(() -> progressBar.setVisibility(View.GONE))                                     
        .subscribe(response -> {                                              
            // Success
            // ...                                                                                         
        }, throwable -> {
            // Fail                                                                
            throwable.printStackTrace();                                                          
            errorStub.inflate();                                                                                          
        });
}

3. And in your test you can override the URL to use MockWebServer's URL.

// Espresso Test
public class ActivityTest {

  @Before
    public void setUp() throws Exception {
        MockWebServer webServer = new MockWebServer();
        HttpUrl url = webServer.url("/");

        // set the url to point to mock webserver in localhost
        MyApplication app = (MyApplication) InstrumentationRegistry.getTargetContext().getApplicationContext();
        app.setUrl(url.toString());
    }

    ....
}

You can also use shared preference to store your server url and override the value during test, but the idea is same: Your view model should not be hardwired to certain URL, but instead inject the URL to the view model. And for resolving dependency like this, Dagger is a good option.

like image 97
pradithya aria Avatar answered Nov 14 '22 16:11

pradithya aria