Hot questions for Using Mockito in android asynctask
Question:
I have a configuration manager that retrieves a json from a remote server. Then it is parsed into a ConfigWrapper object and that object gets returned as a parameter in a provided Callback listener.
So in the test class I call:
@Test public void init_Configuration_With_Network_Load_JSON_From_Server_Return_To_Listener() { mockConnectivityCheck(true); ... mManager.initConfiguration(mContext, eq(anyString()), mListener); verify(mListener, times(1)).onConfigurationLoaded(any(ConfigWrapper.class)); }
mContext is mocked and mListener is also mocked. This will call the class under test's method:
public void initConfiguration(Context context, String url, ConfigurationManagerListener listener){ // Get a reference to shared preferences mSharedPref = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); // If we have network, we should load configurations from remote, otherwise, try to load it from assets folder OR shared pref if (NetworkTools.hasNetworkConnection(context)){ getConfigurationRemote(context, url, listener); } else { getConfigurationOffline(context, listener); } }
So if we have network, we can get the configuration from the server. This method does it:
private void getConfigurationRemote(final Context context, String url, final ConfigurationManagerListener listener) { // Send a request to get the configuration new AsyncTask<String, Void, HashMap<String, Object>> () { @Override protected HashMap<String, Object> doInBackground(String... params) { InputStream in = null; HashMap result = null; try { URL url = new URL(params[0]); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(10000 /* milliseconds */); conn.setConnectTimeout(10000 /* milliseconds */); conn.setRequestMethod("GET"); conn.setDoInput(true); conn.connect(); in = conn.getInputStream(); result = new ObjectMapper().readValue(in, HashMap.class); in.close(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null){ in.close(); } } catch (IOException e) { e.printStackTrace(); } return result; } } @Override protected void onPostExecute(HashMap<String, Object> config) { // We if have a valid result, save the data to shared pref AND save it to a ConfigurationWrapper object // other wise, try to load from assets folder or shared pref if available if (config != null && config.size() > 0){ // We want to save the hierarchy of the JSON so we save its string representation to shared pref JSONObject object = new JSONObject(config); mSharedPref.edit().putString(CONFIGURATIONS_KEY, object.toString()).apply(); mConfigurationWrapper = new ConfigWrapper(config); listener.onConfigurationLoaded(mConfigurationWrapper); } else { // No valid configuration from remote server, so load it from local source getConfigurationOffline(context, listener); } } }.execute(url); }
Now, I'm trying to write a unit test using Mockito (or PowerMockito if needed) that can test this code. I'm not entirely sure how to tackle this situation where I call a method that has a new AsyncTask().execute() call in it. So far, calling the initConfiguration method and mocking the network check to return true, stops after calling execute(). doInBackground() does not seem to get called.
How would you test such code? Thank you!
Answer:
The way your code is set up makes unit testing hard. I'd suggest rearranging it.
The getConfigurationRemote method currently does 3 separate things:
- creates the
AsyncTask
- defines what that task does
- executes the task
My solution:
- move the anonymous
AsyncTask
to its own class. - move the creation of the task (
new AsyncTask(....
) to a factory class or, better yet, use a dependency injection framework like Dagger.
This is how i imagine it to look like in the end:
private void getConfigurationRemote(final Context context, String url, final ConfigurationManagerListener listener) { // create the task ConfigurationRemoteAsyncTask task = taskFactory.createConfigurationRemoteTask(listener); // Send a request to get the configuration task.execute(url); }
Now you can mock and test much more easily:
verify that task.execute is called with the given url when .initConfiguration is called by simply mocking the task returned by the task factory.
verify that listener.onConfigurationLoaded is called when task.onPostExecute is called, without mocking the whole network infrastructure. This might look like this:
@Test public void init_Configuration_With_Network_Load_JSON_From_Server_Return_To_Listener() { ConfigurationRemoteAsyncTask task = new ConfigurationRemoteAsyncTask(mockedListener); HashMap<String, Object> config = getNotEmptyConfig(); task.onPostExecute(config); verify(mockedListener).onConfigurationLoaded(any(ConfigWrapper.class)); }
Question:
I am writing some tests using Mockito. I have a class I want to test that basically makes a synchronous call into an asynchronous one via an AsyncTask.
This is the class to test:
public class GetEntryImpl implements GetEntry { private final DataEntryRepository mDataEntryRepository; public GetEntryImpl() { this(new DataEntryRepositoryImpl()); } public GetEntryImpl(@NonNull final DataEntryRepository repository) { mDataEntryRepository = repository; } @Override public void execute(final long id, final Callback<DataEntry> callback) { AsyncTask.execute(new Runnable() { @Override public void run() { DataEntry dataEntry = mDataEntryRepository.getEntry(id); if (dataEntry != null) { callback.onResult(dataEntry); } else { callback.onError("TODO", null); } } }); } }
And this is my test:
public class GetEntryTest { private GetEntryImpl mGetEntry; @Mock private DataEntryRepository mRepository; @Captor private ArgumentCaptor<Callback> mCallbackArgumentCaptor; private DataEntry mDataEntry; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); mDataEntry = getFakeEntry(); when(mRepository.getEntry(anyLong())).thenReturn(mDataEntry); mGetEntry = new GetEntryImpl(mRepository); } @SuppressWarnings("unchecked") @Test public void testGetEntry_success() throws Exception { mGetEntry.execute(1L, mCallbackArgumentCaptor.capture()); verify(mRepository).getEntry(eq(1L)); Callback<DataEntry> callback = mCallbackArgumentCaptor.getValue(); verify(callback).onResult(eq(mDataEntry)); } }
It gives me this error when running the test (edit: full stack trace):
org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Misplaced argument matcher detected here: -> at com.xyz.interactor.GetEntryTest.testGetEntry_success(GetEntryTest.java:45) You cannot use argument matchers outside of verification or stubbing. Examples of correct usage of argument matchers: when(mock.get(anyInt())).thenReturn(null); doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject()); verify(mock).someMethod(contains("foo")) Also, this error might show up because you use argument matchers with methods that cannot be mocked. Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode(). Mocking methods declared on non-public parent classes is not supported. at com.xyz.interactor.GetEntryTest.testGetEntry_success(GetEntryTest.java:46) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188) at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144) Exception: java.lang.NullPointerException thrown from the UncaughtExceptionHandler in thread "AsyncTask #1" Process finished with exit code 255
Line 45 is the first line of the test:
mGetEntry.execute(1L, mCallbackArgumentCaptor.capture());
Answer:
You do not need an ArgumentCaptor
for Callback
, but a mock.
@SuppressWarnings("unchecked") @Test public void testGetEntry_success() throws Exception { mGetEntry.execute(1L, mCallback); verify(mRepository).getEntry(eq(1L)); verify(mCallback).onResult(eq(mDataEntry)); }
where mCallback
is:
@Mock private Callback<DataEntry> mCallback;
mockito prints an error because you are using an ArgumentCaptor
outside a verify
statement. You can see examples of correct ArgumentCaptor
usage here.
Question:
I am testing an AsyncTask
. I want to stub an HttpURLConnection
to return my mocked object. This is how I do it (PackageDownloader
represents an AsyncTask
):
... PackageDownloader packageDownloader = new PackageDownloader(); packageDownloader.setParameters(URL, downloadFolder, downloadProgressCallback); PackageDownloader mPackageDownloader = spy(packageDownloader); HttpURLConnection connectionMock = Mockito.mock(HttpURLConnection.class); doReturn(0).when(connectionMock).getContentLength(); doReturn(connectionMock).when(mPackageDownloader).createConnection(Mockito.any(URL.class)); mPackageDownloader.execute(); mPackageDownloader.get();
This is PackageDownloader
:
public HttpURLConnection createConnection(URL url) throws IOException { HttpURLConnection connection; connection = (HttpURLConnection) url.openConnection(); return connection; } @Override protected DownloadResult doInBackground(Void... params) { HttpURLConnection connection; URL downloadUrl = new URL(downloadUrlString); connection = createConnection(downloadUrl); long totalBytes = connection.getContentLength(); ...
Here, createConnection returns real, not mocked object, and I can't figure out why.
Answer:
Well I have found a solution, though haven't found an explanation why it works so.
The reason nothing worked was that doInBackground
method is async, I assume, so I had to call it directly via reflection, like so:
Method method = mPackageDownloader.getClass().getMethod("doInBackground", Void[].class); method.invoke(mPackageDownloader, new Void[] {null});