温馨提示:如果你不太熟悉单元测试,可以先看下之前四篇基础框架使用。便于你更好的理解下面的内容。
在平日的开发中,我们用后台写好给我们接口去获取数据。虽然我们有一些请求接口的工具,可以快速的拿到返回数据。但是在一些异常情况的处理上就不太方便了。我列出以下几个痛点:
- 快速的查看返回数据与数据的处理。(一般我们都是将写好的代码跑到手机上,点击到对应页面的对应按钮)
- 异常信息的返回与处理。比如一个接口返回一个列表数据,如果列表为空呢?(找后台给我们模拟数据)网速不好呢?(不知道怎么搞…)网络异常呢?(关闭网络)
- 后台有部分接口没有写好,你就只能等他了。
不知道上面的这三点有没有戳到你的痛处。如果扎心了,那么老铁你就有必要掌握今天的内容。
1. 请求接口
我们就使用网络请求三件套 (retrofit + okhttp + rxjava2) 来举例。
首先添加一下依赖,同时记得加网络权限。
- //RxJava
- compile 'io.reactivex.rxjava2:rxjava:2.1.7'
- //RxAndroid
- compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
- //okhttp
- compile "com.squareup.okhttp3:okhttp:3.9.1"
- //Retrofit
- compile("com.squareup.retrofit2:retrofit:2.3.0") {
- exclude module: 'okhttp'
- }
- compile("com.squareup.retrofit2:adapter-rxjava2:2.3.0") {
- exclude module: 'rxjava'
- }
- compile "com.squareup.retrofit2:converter-gson:2.3.0"
测试接口:
- public interface GithubApi {
- String BASE_URL = "https://api.github.com/";
- @GET("users/{username}")
- Observable<User> getUser(@Path("username") String username);
- }
Retrofit 的初始化,我们使用 LoggingInterceptor 来打印返回数据。
- public class GithubService {
- private static Retrofit retrofit = new Retrofit.Builder()
- .baseUrl(GithubApi.BASE_URL)
- .client(getOkHttpClient())
- .addConverterFactory(GsonConverterFactory.create())
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .build();
- public static GithubApi createGithubService() {
- return retrofit.create(GithubApi.class);
- }
- private static OkHttpClient getOkHttpClient(){
- return new OkHttpClient.Builder()
- .addInterceptor(new LoggingInterceptor())
- .build();
- }
- }
测试代码:
- @RunWith(RobolectricTestRunner.class)
- @Config(constants = BuildConfig.class, sdk = 23)
- public class ResponseTest {
- @Before
- public void setUp() {
- ShadowLog.stream = System.out;
- initRxJava2();
- }
- private void initRxJava2() {
- RxJavaPlugins.reset();
- RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
- @Override
- public Scheduler apply(Scheduler scheduler) throws Exception {
- return Schedulers.trampoline();
- }
- });
- RxAndroidPlugins.reset();
- RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
- @Override
- public Scheduler apply(Scheduler scheduler) throws Exception {
- return Schedulers.trampoline();
- }
- });
- }
- @Test
- public void getUserTest() {
- GithubService.createGithubService()
- .getUser("simplezhli")
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer<User>() {
- @Override
- public void onSubscribe(Disposable d) {}
- @Override
- public void onNext(User user) {
- assertEquals("唯鹿", user.name);
- assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
- }
- @Override
- public void onError(Throwable e) {
- Log.e("Test", e.toString());
- }
- @Override
- public void onComplete() {}
- });
- }
- }
上面的代码中,因为网络请求是异步的,所以我们直接测试是不能直接拿到数据,因此无法打印出 Log 以及测试。所以我们使用 initRxJava2() 方法将异步转化为同步。这样我们就可以看到返回信息。测试结果如下:
上面的例子为了简单直观的说明,所以将请求接口的方法写到了测试类中,实际中我们可以 Mock 方法所在的类直接调用请求方法。配合 Robolectric 对 View 控件的状态进行测试。
如果你觉得每次测试都要加 initRxJava2 这段方法很麻烦,你可以抽象出来,或者使用 @Rule。
- public class RxJavaRule implements TestRule {
- @Override
- public Statement apply(final Statement base, Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- RxJavaPlugins.reset();
- RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
- @Override
- public Scheduler apply(Scheduler scheduler) throws Exception {
- return Schedulers.trampoline();
- }
- });
- RxAndroidPlugins.reset();
- RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
- @Override
- public Scheduler apply(Scheduler scheduler) throws Exception {
- return Schedulers.trampoline();
- }
- });
- base.evaluate();
- }
- };
- }
- }
2. 模拟数据
1. 使用拦截器模拟数据
利用 okhttp 的拦截器模拟响应数据。
- public class MockInterceptor implements Interceptor {
- private final String responseString; //你要模拟返回的数据
- public MockInterceptor(String responseString) {
- this.responseString = responseString;
- }
- @Override
- public Response intercept(Interceptor.Chain chain) throws IOException {
- Response response = new Response.Builder()
- .code(200)
- .message(responseString)
- .request(chain.request())
- .protocol(Protocol.HTTP_1_0)
- .body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()))
- .addHeader("content-type", "application/json")
- .build();
- return response;
- }
- }
测试代码:
- @RunWith(RobolectricTestRunner.class)
- @Config(constants = BuildConfig.class, sdk = 23)
- public class MockGithubServiceTest {
- private GithubApi mockGithubService;
- @Rule
- public RxJavaRule rule = new RxJavaRule();
- @Before
- public void setUp() throws URISyntaxException {
- ShadowLog.stream = System.out;
- //定义Http Client,并添加拦截器
- OkHttpClient okHttpClient = new OkHttpClient.Builder()
- .addInterceptor(new LoggingInterceptor())
- .addInterceptor(new MockInterceptor("json数据"))//<-- 添加拦截器
- .build();
- //设置Http Client
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl(GithubApi.BASE_URL)
- .client(okHttpClient)
- .addConverterFactory(GsonConverterFactory.create())
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .build();
- mockGithubService = retrofit.create(GithubApi.class);
- }
- @Test
- public void getUserTest() throws Exception {
- mockGithubService.getUser("weilu") //<-- 这里传入错误的用户名
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer<User>() {
- @Override
- public void onSubscribe(Disposable d) {}
- @Override
- public void onNext(User user) {
- assertEquals("唯鹿", user.name);
- assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
- }
- @Override
- public void onError(Throwable e) {
- Log.e("Test", e.toString());
- }
- @Override
- public void onComplete() {}
- });
- }
- }
虽然我们传入了错误的用户名,但是我们模拟的响应信息已经提前设定好了,所以测试结果不变。
利用这个思路,我们可以修改 MockInterceptor 的 code,模拟 404 的情况。
2.MockWebServer
MockWebServer 是 square 出品的跟随 okhttp 一起发布,用来 Mock 服务器行为的库。MockWebServer 能帮我们做的事情:
- 可以设置 http response 的 header、body、status code 等。
- 可以记录接收到的请求,获取请求的 body、header、method、path、HTTP version。
- 可以模拟网速慢的网络环境。
- 提供 Dispatcher,让 mockWebServer 可以根据不同的请求进行不同的反馈。
添加依赖:
- testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
测试代码:
- @RunWith(RobolectricTestRunner.class)
- @Config(constants = BuildConfig.class, sdk = 23)
- public class MockWebServerTest {
- private GithubApi mockGithubService;
- private MockWebServer server;
- @Rule
- public RxJavaRule rule = new RxJavaRule();
- @Before
- public void setUp(){
- ShadowLog.stream = System.out;
- // 创建一个 MockWebServer
- server = new MockWebServer();
- //设置响应,默认返回http code是 200
- MockResponse mockResponse = new MockResponse()
- .addHeader("Content-Type", "application/json;charset=utf-8")
- .addHeader("Cache-Control", "no-cache")
- .setBody("{\"id\": 12456431, " +
- " \"name\": \"唯鹿\"," +
- " \"blog\": \"http://blog.csdn.net/qq_17766199\"}");
- MockResponse mockResponse1 = new MockResponse()
- .addHeader("Content-Type", "application/json;charset=utf-8")
- .setResponseCode(404)
- .throttleBody(5, 1, TimeUnit.SECONDS) //一秒传递5个字节,模拟网速慢的情况
- .setBody("{\"error\": \"网络异常\"}");
- server.enqueue(mockResponse); //成功响应
- server.enqueue(mockResponse1);//失败响应
- OkHttpClient okHttpClient = new OkHttpClient.Builder()
- .addInterceptor(new LoggingInterceptor())
- .build();
- Retrofit retrofit = new Retrofit.Builder()
- .baseUrl("http://" + server.getHostName() + ":" + server.getPort() + "/") //设置对应的Host与端口号
- .client(okHttpClient)
- .addConverterFactory(GsonConverterFactory.create())
- .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
- .build();
- mockGithubService = retrofit.create(GithubApi.class);
- }
- @Test
- public void getUserTest() throws Exception {
- //请求不变
- mockGithubService.getUser("simplezhli")
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(new Observer<User>() {
- @Override
- public void onSubscribe(Disposable d) {
- }
- @Override
- public void onNext(User user) {
- assertEquals("唯鹿", user.name);
- assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
- }
- @Override
- public void onError(Throwable e) {
- Log.e("Test", e.toString());
- }
- @Override
- public void onComplete() {
- }
- });
- //验证我们的请求客户端是否按预期生成了请求
- RecordedRequest request = server.takeRequest();
- assertEquals("GET /users/simplezhli HTTP/1.1", request.getRequestLine());
- assertEquals("okhttp/3.9.1", request.getHeader("User-Agent"));
- // 关闭服务
- server.shutdown();
- }
- }
代码中的注释写的很清楚了,就不用过多的解释了,使用起来非常的简单。
执行结果(成功):
失败结果:
默认情况下 MockWebServer 预置的响应是先进先出的。这样可能对你的测试有限制,这时可以通过 Dispatcher 来处理,比如通过请求的路径来选择转发。
- Dispatcher dispatcher = new Dispatcher() {
- @Override
- public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
- if (request.getPath().equals("/users/simplezhli")){
- return new MockResponse()
- .addHeader("Content-Type", "application/json;charset=utf-8")
- .addHeader("Cache-Control", "no-cache")
- .setBody("{\"id\": 12456431, " +
- " \"name\": \"唯鹿\"," +
- " \"blog\": \"http://blog.csdn.net/qq_17766199\"}");
- } else {
- return new MockResponse()
- .addHeader("Content-Type", "application/json;charset=utf-8")
- .setResponseCode(404)
- .throttleBody(5, 1, TimeUnit.SECONDS) //一秒传递5个字节
- .setBody("{\"error\": \"网络异常\"}");
- }
- }
- };
- server.setDispatcher(dispatcher); //设置Dispatcher
通过上面的例子,是不是可以很好的解决你的痛点,希望对你有帮助。只要我们有和后台有开发文档,约定好数据格式与字段名,那么我们可以更敏捷的去做我们的开发。本篇所有代码已上传至 Github 。希望大家多多点赞支持!