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:
- mock a singleton
- 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.