- How to test a singleton in an Android Service (1)?
- This article has been authorized by the original author Songzhw
- The Nuggets translation Project
- Translator: Newt0n
- Proofreading: Graning, DeadLion
Recently I had a big problem: how do I test singleton patterns in my service? Finally I solved the problem. And I think the whole problem-solving process is a great opportunity to explain unit tests clearly to the reader. Due to space limitations, this is the first article and I will write another one later.
Our services
// [PushService.java] public class PushService extends Service { public void onMessageReceived(String id, Bundle data){ FooManager.getInstance().receivedMsg(data); }}Copy the code
FooManager is an example:
// [FooManager.java]
public class FooManager {
private static FooManager instance = new FooManager();
private FooManager(){}
public static FooManager getInstance(){
return instance;
}
public void receivedMsg(Bundle data){
}
}
Copy the code
How should we test PushService?
Obviously, we want to make sure FooManager calls receiveMsg(), so we want something like this:
verify(fooManager).receiveMsg(data);
Copy the code
As any developer who knows Mockito knows, when we call Verify (fooManager) we must first make fooManager a mock object; Otherwise, the program will throw an exception: org. Mockito. Exception. Misusing. NotAMockException
So we need to simulate an instance of FooManager. Now I break down the test steps into two small tests:
- Simulate a singleton
- Simulate a singleton in the service
Simulation singleton (1)
Step 1: Use MockitoFooManager
(failure)
Write a test case first:
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
The program throws an exception when running this use case:
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
This is because Mockito cannot simulate a static method, in this case the getInstance() method.
Step 2: Use PowerMock
Fortunately, I knew that PowerMock can emulate static methods, so I thought I’d switch to PowerMock.
@RunWith(PowerMockRunner.class) @PrepareForTest(FooManager.class) public class FooManagerTest { @Test public void testSingleton(){ FooManager mgr = Mockito.mock(FooManager.class); PowerMockito.mockStatic(FooManager.class); Mockito.when(FooManager.getInstance()).thenReturn(mgr); FooManager actual = FooManager.getInstance(); assertEquals(mgr, actual); }}Copy the code
Yes, I succeeded. It is important to note that this code will only work if your project is a pure Java project and not an Android one. If you want to test your Android project code, there are a few other issues.
Testing Android code
Step 3: Test your Android code with unit tests
You might think that since Android projects are also written in Java, it would be possible to write unit test cases in the $module$/ SRC /test directory as well.
But can it? Let’s look at an example of using JUnit Test to Test Android library code.
@Test
public void testAndroidCode(){
instance.setArgu(argu);
instance.doSomething();
verify(argu).isCalled();
}
Copy the code
However, you may encounter an error: Java. Lang. NoClassDefFoundError: org/apache/HTTP/cookie/cookie
Other classes may not be found, such as Android /util/Log, Android/Content /Context, etc.
The NoClassDefFoundError error is reported because JUnit is running on the JVM, which means JUnit does not have an Android runtime.
In fact, there is an official Android environment under the test scheme: Instrumentation testing.
But that’s not really what we want. Each time you run Instrumentation, you must build the entire project and push the APK file to the mobile device or emulator. So, it’s going to be slow. It doesn’t run directly on a computer like JUnit does (PC/Mac/Linux) and doesn’t require an Android runtime. The result is that running JUnit tests on your computer is much faster than Instrumentation tests.
Is there a solution that includes an Android environment that runs on a computer and can perform tests quickly? Of course, otherwise why would I write this article? The answer is Robolectric!
Step 4: Robolectric
As mentioned earlier, running tests on an Android emulator or physical device is slow. Building, deploying, and launching an application usually takes a minute or more, which is no way to do TEST-driven development.
Robolectric is a framework that lets you run Android tests directly inside the IDE.
What did Robolectric do? This is a bit complicated, but it’s easy to think of Robolectric as encapsulating an Android.jar file inside it. This gives you an Android runtime environment, so you can run tests of Android code on your computer.
Here’s an example of Robolectric:
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
@Test
public void clickingButton_shouldChangeResultsViewText() 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 the theme, With Robolectric, we were finally able to test our service directly in a computer environment, and quickly.
Conclusion 01
I showed you how to use Robolectric to quickly test Android code and how to simulate singleton patterns in a Java environment.
But I have to warn you that we still can’t successfully emulate the singleton pattern in the Android environment. I’ll discuss how to solve this problem in my next article.
How to Test the Singleton pattern in Android Services (2)