When writing local unit tests in Android, one of the limitiations that you face is that the tests are run against a version of android.jar that does not have any code. As the documentation explains, any dependency on Android code must be mocked.
A quick example of a simple unit test:
public class ClassUnderTest { public String methodUnderTest(String str) { if (PhoneNumberUtils.isGlobalPhoneNumber(str)) { return "yes"; } else { return "no"; } } } @RunWith(JUnit4.class) public class TestThatFails { private ClassUnderTest classUnderTest; @Before public void setup() { classUnderTest = new ClassUnderTest(); } @Test public void testTheClass() { String result = classUnderTest.methodUnderTest("1234"); assertEquals("yes", result); } }
When this test is run, it will fail with the following error:
java.lang.RuntimeException: Method isGlobalPhoneNumber in android.telephony.PhoneNumberUtils not mocked. See http://g.co/androidstudio/not-mocked for details
The class we are testing has a dependency on the Android utility library PhoneNumberUtils. In order for the test to run successfully, this Android dependency must be mocked.
All the example code for this post is available at this gist.
Mockito: No to Static Methods
Google’s suggested way to mock Android dependencies is to use Mockito. This would generally be fine, however in our example this will not work because Mockito does not support mocking static methods.
This discussion shows that the Mockito contributors consider static methods to be an anti-pattern for various reasons, e.g.
- The dependency on the static method becomes hard wired in the code.
- This makes mocking and testing difficult.
Hence they do not support it as they don’t want to encourage poor design.
So what are some other way to make our test work?
- If this was plain old Java instead of Android, I could have used PowerMockito to mock the static methods. However I have found using PowerMock to be problematic in Android.
- If you only use a few static methods, you could just copy the code into your app assuming the the source was available. Of course this means more code to maintain, and is not sustainable if you use a lot of static methods.
- You could wrap the static method call and internally delegate to the static method. The wrapper could then be mocked. This is the option we will be discussing.
Wrapper Classes
One solution is to create a wrapper class for the Android classes that have the static method, and add that wrapper as a dependency.
public class PhoneNumberUtilsWrapper { public boolean isGlobalPhoneNumber(String phoneNumber) { return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber); } } public class ClassUnderTestWithWrapper { private PhoneNumberUtilsWrapper wrapper; public ClassUnderTestWithWrapper(PhoneNumberUtilsWrapper wrapper) { this.wrapper = wrapper; } public String methodUnderTest(String str) { if (wrapper.isGlobalPhoneNumber(str)) { return "yes"; } else { return "no"; } } }
Here I have created a wrapper class for PhoneNumberUtils which is now a dependency of the class under test.
@RunWith(JUnit4.class) public class TestWithWrapper { @Mock PhoneNumberUtilsWrapper wrapper; private ClassUnderTestWithWrapper classUnderTest; @Before public void setup() { MockitoAnnotations.initMocks(this); classUnderTest = new ClassUnderTestWithWrapper(wrapper); } @Test public void testTheClass() { when(wrapper.isGlobalPhoneNumber(anyString())).thenReturn(true); String result = classUnderTest.methodUnderTest("1234"); assertEquals("yes", result); } }
Because the wrapper class can be mocked, and the method call in the class under test is not static, the test can now pass.
One problem with this solution is when the class under test depends on static methods from many Android libraries. For instance, what happens if the class under test also needs to use TextUtils, DateUtils, etc. Suddenly you end up with lots more boilerplate code, more constructor parameters, etc.
Wrapper Methods
Another way is to wrap the static method call in a non-static method in the class under test.
public class ClassUnderTestWithWrappedMethod { public String methodUnderTest(String str) { if (isGlobalPhoneNumber(str)) { return "yes"; } else { return "no"; } } // can't be private access boolean isGlobalPhoneNumber(String phoneNumber) { return PhoneNumberUtils.isGlobalPhoneNumber(phoneNumber); } }
In order for this to work, in the test we have to use Mockito spy. Also note that the wrapped methods have to be accessible in the test, and therefore can’t be private.
@RunWith(JUnit4.class) public class TestWithWrappedMethod { private ClassUnderTestWithWrappedMethod classUnderTest; private ClassUnderTestWithWrappedMethod classUnderTestSpy; @Before public void setup() { MockitoAnnotations.initMocks(this); classUnderTest = new ClassUnderTestWithWrappedMethod(); classUnderTestSpy = Mockito.spy(classUnderTest); } @Test public void testTheClass() { doReturn(true).when(classUnderTestSpy) .isGlobalPhoneNumber(anyString()); String result = classUnderTestSpy.methodUnderTest("1234"); assertEquals("yes", result); } }
Here we are running the test on the spy class, which will delegate method calls to the real class under test. However we can create stubs for the methods that wrap the static method calls to Android libraries.
As I have mentioned, one disadvantage is that the wrapped methods cannot be private, which is not ideal from an OO design point of view. But then you have to make similar compromises if you are using libraries such as Dagger or Butterknife.
Conclusion
Both of these wrapping solutions can work, but it’s probably a good idea to be consistent and stick with doing it one way if you can. Which method works better may depend on your app architecture, e.g. are you using dependency injection.
Static Methods: Good or Bad? Does it matter?
In this post I am not getting into the argument about whether static methods are good or bad design (although my personal opinion is that their use should be limited). There are plenty of arguments about this issue on the internet already.
However, in the Java world they are a fact of life.
Many utility classes in Java, Android and many popular libraries are not real OO classes, but collections of procedural functions. The functions are often written as static methods and grouped by functionality.
Whether you like static methods or not, we all have to learn to deal with them in a pragmatic way.