2

I have an Android activity, in which there are calls like

final ConnectToServerAsyncTask task = new ConnectToServerAsyncTask(...);

and

final Intent intent = new Intent(this, SomeActivity.class);

In order to unit test this class, I need to be able to mock the creation of ConnectToServerAsyncTask and Intent (e. g. using Mockito).

Are there more elegant methods than the one described below?

public class MainActivityOfTheApp extends Activity {
    private IAsyncTaskFactory asyncTaskFactory = new AsyncTaskFactory();
    private IIntentFactory intentFactory = new IntentFactory();

    public void setAsyncTaskFactory(final IAsyncTaskFactory aFactory)
    {
        asyncTaskFactory = aFactory;
    }

    public void setIntentFactory(final IIntentFactory aFactory)
    {
        intentFactory = aFactory;
    }

    @Override
    protected void onResume() {
        ...
        final ConnectToServerAsyncTask task = asyncTaskFactory.create(...);
        ...

        final Intent intent = intentFactory.create(this, OtherActivity.class);
        ...
    }
}

In a unit test, I would create an instance of MainActivityOfTheApp and then inject mocks using setAsyncTaskFactory and setIntentFactory.

Glory to Russia
  • 17,289
  • 56
  • 182
  • 325
  • 1
    Your solution is also the most clever that I can think of. I, too, would very much like to know if there is another way of doing it. – Vering Aug 27 '13 at 09:18

1 Answers1

5

PowerMock

You can mock constructors using PowerMock.

So in your case the test would look like something like this:

@RunWith(PowerMockRunner.class)
@PrepareForTest(MainActivityOfTheApp.class)
public class MainActivityOfTheAppTest{
  private AsyncTaskFactory asyncTaskFactory;
  private IntentFactory intentFactory;
  private MainActivityOfTheApp mainActivityOfTheApp;

  @Before
  public void prepare() {
    asyncTaskFactory = PowerMockito.mock(AsyncTaskFactory.class);
    intentFactory = PowerMockito.mock(IntentFactory.class);
    PowerMockito.whenNew(AsyncTaskFactory.class).withNoArguments().thenReturn(asyncTaskFactory);
    PowerMockito.whenNew(IntentFactory.class).withNoArguments().thenReturn(intentFactory);
    mainActivityOfTheApp = new MainActivityOfTheApp();
  }

  @Test
  public void doTest() {
    //mainActivityOfTheApp has the mocks in final field inside, no need for the setters.
  }
}

I have to note, that PowerMock is powerful, but often has problems with complicated classloading (for example OSGI environments), or code coverage tools (jacoco for example). It works usually, but I have wasted some time with it.

Without PowerMock

Second possibility is to create package private(No modifier. Why not protected or public? See What's wrong with overridable method calls in constructors?) methods, which call the constructor like this:

public class MainActivityOfTheApp extends Activity {
    private final IAsyncTaskFactory asyncTaskFactory = constructAsyncTaskFactory();
    private final IIntentFactory intentFactory = constructIntentFactory();

    IAsyncTaskFactory constructAsyncTaskFactory()
    {
        return new AsyncTaskFactory();
    }

    IIntentFactory constructIntentFactory()
    {
        return new IntentFactory();
    }
    ...
}

Then in your unit test (the test must be in the same package as the tested class!) you override the construct*() methods:

public class MainActivityOfTheAppTest{
  private AsyncTaskFactory asyncTaskFactory;
  private IntentFactory intentFactory;
  private MainActivityOfTheApp mainActivityOfTheApp;

  @Before
  public void prepare() {
    asyncTaskFactory = mock(AsyncTaskFactory.class);
    intentFactory = mock(IntentFactory.class);
    mainActivityOfTheApp = new HackedMainActivityOfTheApp();
  }

  @Test
  public void doTest() {
    //mainActivityOfTheApp has the mocks in final field inside, no need for the setters.
  }

  private class HackedMainActivityOfTheApp extends MainActivityOfTheApp {
        IAsyncTaskFactory constructAsyncTaskFactory()
        {
            return asyncTaskFactory;
        }

        IIntentFactory constructIntentFactory()
        {
            return intentFactory;
        }
  }
}

You can protect the construct* methods if you sign the jar containing the MainActivityOfTheApp. See https://stackoverflow.com/a/968612/337621 :

Also, patches are harder (you have to re-sign the jar), class-patches are impossible (all classes in a single package must have the same signature source) and splitting jars becomes a chore.

Community
  • 1
  • 1
Gábor Lipták
  • 9,646
  • 2
  • 59
  • 113