使用Mockito、Robolectric和RxJava及Retrofit进行单元测试

原文出处:http://www.jianshu.com/p/b00586534fc1 

本文只是对Mockito、Robolectric的测试使用进行简单介绍,主要还是围绕着Http测试这一块,特别是RxJava和Retrofit与上述两个测试框架的结合使用。

PS:RxJava这一块我看的是大头鬼前辈的文章,也推荐对RxJava感兴趣的朋友去看。

本文结构

  • build.gradle结构

  • 配置并编写单元测试

  • api测试

build.gralde结构

robolectric3.0版本使用的时候遇到个问题,然后大概查阅了解到是gradle插件1.3.0版本与robolectric的冲突,所以这个demo里面用到的gradle.tools用的是1.2.3版本。
然后就是用到了java8的插件,所以配置了一下,这里就不用多说了。

配置并编写单元测试

blob.png

如图所示,打开Build Variants选项卡,然后将Test Artifact的类型选为Unit Test。

blob.png

然后创建对应的包名,巴拉巴拉。

然后开始写第一个测试,下面就直接贴代码了。

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
public class MockitoTest {
    @Before
    public void setUp(){
    }
    @Test
    public void MocksTest(){
        TestSubscriber<Mock> testSubscriber = new TestSubscriber<>();
        getMocks().subscribe(testSubscriber);
        assertThat(testSubscriber.getOnNextEvents().size(),is(3));
    }
    @Test
    public void ListMocksTest(){
        TestSubscriber<Mock> testSubscriber = new TestSubscriber<>();
        getListMocks().flatMap(Observable::from).subscribe(testSubscriber);
        assertThat(testSubscriber.getOnNextEvents().size(), is(3));
    }
    private Observable<Mock> getMocks(){
        return Observable.just(new Mock(),new Mock(),new Mock());
    }
    private Observable<List<Mock>> getListMocks(){
        List<Mock> list = new ArrayList<>();
        list.add(new Mock());
        list.add(new Mock());
        list.add(new Mock());
        return Observable.just(list);
    }
    class Mock{
    }
}

上面代码里面是两个测试方法,然后有两个获取测试数据的函数,这两个函数其实是类似的,分别是返回Observable和Observable<List>,这里两个测试方法其实目的是一样的,都是判断个数是否为3。这里其实就是想展示使用变换操作符FlatMap,将Observable<List>转换为Observable。

还有就是import了需要的assertThat和is。这里再额外说下,TestSubscriber这个类是rx包名下的类,是用于Observable类测试用的,这里再额外提一下,一个别人写的类,可以实现相同的功能,有兴趣的朋友可以看下这篇文章

api测试

下面贴api相关的测试代码。

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class)
public class ApiTest {
    Github mGithub = GithubFactory.getSingleton();//走真实数据的接口
    Github mMockGithub;
    Client mMockClient;
    @Before public void setUp() {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
            .serializeNulls()
            .create();
        mMockClient = mock(Client.class);
        RequestInterceptor requestInterceptor = request -> request.addHeader("User-Agent",
            "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0");
        RestAdapter restAdapter = new RestAdapter.Builder().setClient(mMockClient)
            .setLogLevel(RestAdapter.LogLevel.FULL)
            .setEndpoint("https://api.github.com")
            .setConverter(new GsonConverter(gson))
            .setRequestInterceptor(requestInterceptor)
            .build();
        mMockGithub = restAdapter.create(Github.class);
    }
    @Test public void reposTest1() throws IOException {
        String mockJsonResult =
            "这里模拟数据太长就不贴了,请看代码吧";
        FakeHttp.addPendingHttpResponse(200, mockJsonResult);
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        HttpResponse httpResponse = new DefaultHttpClient().execute(httpGet);
        String result = EntityUtils.toString(httpResponse.getEntity());
        System.out.print(result);
        assertThat(result, is(mockJsonResult));
    }
    //这个是走真实网络返回的数据
    @Test public void reposTest2() {
        List<Repo> list = mGithub.listRepos("devinshine");
        assertThat(list.size(), is(not(0)));
        System.out.print(list.size());
    }
    //这个是走真实网络返回的数据
    @Test public void reposTestByObservable() {
        int size = mGithub.listRepos2Observable("devinshine")
            .flatMap(Observable::from)
            .count()
            .toBlocking()
            .single();
        assertThat(size, is(not(0)));
        System.out.print(size);
        //下面代码是会报错的
        //TestSubscriber<Repo> testSubscriber = new TestSubscriber<>();
        //mGithub.listRepos2Observable("devinshine")
        //    .flatMap(Observable::from)
        //    .subscribe(testSubscriber);
        //assertThat(testSubscriber.getOnNextEvents().size(),is(not(0)));
    }
    //这是走模拟数据
    @Test public void reposTestByMockClient() throws IOException {
        String mockJsonResult =
            "这里模拟数据太长就不贴了,请看代码吧";
        Response response =
            new Response("http://www.baidu.com", 200, "nothing", Collections.EMPTY_LIST,
                new TypedByteArray("application/json", mockJsonResult.getBytes()));
        when(mMockClient.execute(Matchers.anyObject())).thenReturn(response);
        int size = mMockGithub.listRepos2Observable("devinshine")
            .flatMap(Observable::from)
            .count()
            .toBlocking()
            .single();
        assertThat(size, is(1));
    }

上面有4个测试函数。
第一个测试函数就是最基本的模拟http层的返回内容,使用的robolectric自带的方法。
第二个测试函数是使用retrofit进行网络请求,是直接返回网络真实请求的数据,这个也十分的简单易懂。
第三个测试函数是使用retrofit和RxJava的Observable,但是是用异步的方式获取数据,也是网络真实请求的数据,这个时候我们要获取返回的数据个数的话,就要将异步转为同步,不然的话直接去获取个数是会为0的;
第四个测试函数和第三个函数整体类似,只是走的不是真实网络请求,而是通过模拟数据的形式进行测试,这里用到了mockito的when方法,mockito这个框架在这里就不多作介绍,简单的说下when和thenReturn的作用是什么意思,用大白话来解释就是,当在做某事的时候,返回某个值。
那么

when(mMockClient.execute(Matchers.anyObject())).thenReturn(response);

这段就很好理解了,当mMockClient执行execute方法的时候,返回response这个值,其实也就是替换真实的网络请求。

至此,一个简单的demo就写完了,具体相关框架的引申讲解本次就不再介绍了,相信网上已经有很多介绍了,如果有机会我再补充一些我所理解TDD运用在Android开发上的看法(挖坑);

demo地址:https://github.com/DevinShine/MockTest

参考资料:

1.https://touk.pl/blog/2014/02/26/mock-retrofit-using-dagger-and-mockito/
2.https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-19/RxJava-Observables%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95.md
3.http://qiuguo0205.iteye.com/blog/1443344
4.http://www.slideshare.net/ssuser72c3b0/rxjava-android-rxjava
5.http://mcxiaoke.gitbooks.io/rxdocs/content/Observables.htmlttp://support.ghost.org/installing-ghost-linux/
6.http://blog.csdn.net/lzyzsd/article/details/41833541