Android async function unit testing

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
  }
}
We are aware that the default behavior is problematic when using classes like Log or TextUtils and will evaluate possible solutions in future releases.

 

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

mockito website

powermock site

 

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

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);
    }
}

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327091412&siteId=291194637