Recently I had a big problem : How to test singleton in a Service? I finally managed to fix this problem. But I think the process of how to solve this is a great opportunity to explain unit test clearly. So I write this post.

Our Service

// [PushService.java]
public class PushService extends Service {
    public void onMessageReceived(String id.Bundle data) {FooManager.getInstance().receivedMsg(data); }}Copy the code

And our FooManager is an instance:

// [FooManager.java]
public class FooManager {
    private static FooManager instance = new FooManager(a);private FooManager() {}public static FooManager getInstance() {return instance;
    }

    public void receivedMsg(Bundle data) {}}Copy the code

So what should we test the PushServie?

Of coures, we want to make sure the FooManager do call receiveMsg(). So what we want is like this:

verify(fooManager).receiveMsg(data);Copy the code

Anyone who knows Mockito knows you have to make fooManager as a mocked object when you call verify(fooManager). Otherwise, you will get an exception : org.mockito.exception.misusing.NotAMockException

So we should focus on mocking an instance of FooManager. I break down the test into two small tests:

  1. mock a singleton
  2. mock a singleton in a Service

Step 01 : Use Mockito to mock FooManager (Failed)

Firstly, I write a test:

public class FooManagerTest {
    @Test
    public void testSingleton() {FooManager mgr = Mockito.mock(FooManager.class);
        Mockito.when(FooManager.getInstance()).thenReturn(mgr);

        FooManager actual = FooManager.getInstance(); assertEquals(mgr, actual); }}Copy the code

But running this test, I failed and got an exception:

org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

Copy the code

Actually, this is because Mockito cannot mock a static method, in this case, getInstance().

Test Android Code

Step 04 : Robolectric

Like I said, running tests on Android emulator or device is slow. Building, deploying and launch the app often takes a minute or more. There’s no way to do TDD.

Robolectric is a framework that helps you to run you Android tests directly from inside you IDE.

What does Robolectric do? It’s complex, but you can simply think that Robolectric encapsulate a Android.jar inside it. So you now have the android environment, therefore you can test Android code in your computer.

Here is an example of Robolectric:

@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {

  @Test
  public void clickingButton_shouldChangeResultsViewText(a)throws Exception {
    MyActivity activity = Robolectric.setupActivity(MyActivity.class);

    Button button = (Button) activity.findViewById(R.id.button);
    TextView results = (TextView) activity.findViewById(R.id.results);

    button.performClick();
    assertThat(results.getText().toString()).isEqualTo("Robolectric Rocks!"); }}Copy the code

Back to our topic, with the help of Robolectric, we can test our Service in our computer, which is faster.

Conclusion 01

I introduce use Robolectric to test Android code in a fast speed, and how to mock a singleton in java environment.

But I have to tell you, if you want to mock a Singleton in Android environment, you will fail. And that part is what I will talk about in the later post.