Most of the content is also suitable for Java. Here, we mainly do unit tests for modules in the sdk category, not involving UI.
1. Configuration
1) Return the default object for the android native class library, otherwise, you need to manually mock when you encounter a statement such as Log, but don't expect this configuration to provide full support for the android API
testOptions { unitTests.returnDefaultValues = true }
Refer to the description hereUnit testing support
"Method ... not mocked."
The android.jar file that is used to run unit tests does not contain any actual code - that is provided by the Android system image on real devices. Instead, all methods throw exceptions (by default). This is to make sure your unit tests only test your code and do not depend on any particular behaviour of the Android platform (that you have not explicitly mocked e.g. using Mockito). If that proves problematic, you can add the snippet below to your build.gradle to change this behavior:
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
2) Configure the dependency library
testCompile 'junit:junit:4.12' testCompile "org.mockito:mockito-core:1.10.+" testCompile ('org.powermock:powermock-api-mockito:1.6.3') { exclude module: 'hamcrest-core' exclude module: 'objenesis' } testCompile ('org.powermock:powermock-module-junit4:1.6.3') { exclude module: 'hamcrest-core' exclude module: 'objenesis' }
where powermock is used to provide mock support for static and final methods
In addition, some people have questioned powermock. The general reason is that in order to realize the mocking of static and final methods, powermock has partially modified the bytecode of the tested class in the jvm (only modified when the test is running, It will not affect your source code), so the actual test class is partially different from the one you wrote yourself, and further questions about the usage of static are raised, see:
PowerMock + Mockito VS Mockito alone
Why is wide usage of PowerMock problematic
3) Set up Android Studio
Left column Build Variants -> Test Artifact select Unit Tests
2. Unit testing for single-process asynchronous requests
First there is the following class
public class JustAClass { //callback interface public interface JustACallBack { void callFunc(JustAResult result); } //Data parent interface carried in callback public interface JustAResult {} // data class for testing public class ImplementedResult implements JustAResult{ public ImplementedResult(String content) {this.content = content;} public String content; } // test function here public void mainThreadFunc(final JustACallBack callBack) { callBack.callFunc(new ImplementedResult("can you reach me")); } }
Mockito provides ArgumentCaptor for getting the result of an asynchronous callback
public class JustAClassTest { @Captor ArgumentCaptor<JustAClass.JustAResult> captor; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void testMainThreadFunc() throws Exception { JustAClass justAClass = new JustAClass(); JustAClass.JustACallBack callBack = Mockito.mock(JustAClass.JustACallBack.class); justAClass.mainThreadFunc(callBack); Mockito.verify(callBack).callFunc(captor.capture()); Assert.assertEquals(((JustAClass.ImplementedResult) captor.getValue()).content, "can you reach me"); } }
3. If the function under test starts a child thread and then calls the callback interface in the child thread, the above test is invalid
Generally mockito will report the error Wanted but not invoked, the error is in
Mockito.verify(callBack).callFunc(captor.capture());
The reason is that the child thread has not ended, and the main process has already started to verify the result.
The class to be tested first
public class JustAClass { //callback interface public interface JustACallBack { void callFunc(JustAResult result); } //Data parent interface carried in callback public interface JustAResult {} // data class for testing public class ImplementedResult implements JustAResult{ public ImplementedResult(String content) {this.content = content;} public String content; } // test function here public void mainThreadFunc(final JustACallBack callBack) { callBack.callFunc(new ImplementedResult("can you reach me")); } // test function here public void subThreadFunc(final JustACallBack callBack) { new Thread(new Runnable() { @Override public void run() { //Simulate time-consuming operation for 0.5s try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace (); } callBack.callFunc(new ImplementedResult("can you reach me")); } }).start(); } }
At this time, you can wait for the end of the child thread by locking, and the CountDownLatch used here
A CountDownLatch
is initialized with a given count. The await
methods block until the current count reaches zero due to invocations of the countDown()
method, after which all waiting threads are released and any subsequent invocations of await
return immediately. This is a one-shot phenomenon -- the count cannot be reset.
public class JustAClassTest { @Captor ArgumentCaptor<JustAClass.JustAResult> captor; CountDownLatch latch; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); //for child thread async latch = new CountDownLatch(1); } @Test public void testMainThreadFunc() throws Exception { JustAClass justAClass = new JustAClass(); JustAClass.JustACallBack callBack = Mockito.mock(JustAClass.JustACallBack.class); justAClass.mainThreadFunc(callBack); Mockito.verify(callBack).callFunc(captor.capture()); Assert.assertEquals(((JustAClass.ImplementedResult) captor.getValue()).content, "can you reach me"); } @Test public void testSubThreadFunc() throws Exception { JustAClass justAClass = new JustAClass(); justAClass.subThreadFunc(new JustAClass.JustACallBack() { @Override public void callFunc(JustAClass.JustAResult result) { Assert.assertTrue(result instanceof JustAClass.ImplementedResult); JustAClass.ImplementedResult resultReal = (JustAClass.ImplementedResult) result; Assert.assertEquals("can you reach me", resultReal.content); latch.countDown(); } }); //wait 1s maximum before callback returns latch.await(1000, TimeUnit.MILLISECONDS); } }