Hot questions for Using Mockito in android asynctask

Top 10 Java Open Source / Mockito / 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});