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:
Activity
's onCreate
method can be nullified or controlled?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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With