Tip: If you’re not familiar with unit testing, take a look at the previous four basic Framework Uses. So that you can better understand the following content.

In daily development, we use the backend written to give us the interface to get data. Although we have some tools to request the interface, can get the return data quickly. However, it is not convenient to handle some exceptions. Here are some pain points:

  • A quick look at the returned data and the processing of the data. (Generally, we run the written code onto the phone and click the corresponding button on the corresponding page)

  • Return and processing of exception information. For example, if an interface returns a list of data, what if the list is empty? (Find the background to give us simulation data) the network speed is bad? (I don’t know how to…) Is the network abnormal? (Shut down the network)

  • Part of the background interface is not written well, you can only wait for him.

I wonder if the above three points have touched a nerve. If you do, then you need to master today’s content.

1. Request interface

Let’s use a web request three-piece set (Retrofit + OKHTTP + rxJavA2) as an example.

First add the dependencies and remember to add network permissions.

/ / RxJava compile 'IO. Reactivex. Rxjava2: RxJava: 2.1.7' / / RxAndroid compile 'IO. Reactivex. Rxjava2: RxAndroid: 2.0.1' / / okhttp The compile "com. Squareup. Okhttp3: okhttp: 3.9.1" / / Retrofit the compile (" com. Squareup. Retrofit2: Retrofit: 2.3.0 ") {exclude The module: 'okhttp'} the compile (" com. Squareup. Retrofit2: adapter - rxjava2:2.3.0 ") {exclude the module: 'rxjava} the compile "com. Squareup. Retrofit2: converter - gson: 2.3.0." "Copy the code

Test interface:

public interface GithubApi {

    String BASE_URL = "https://api.github.com/";

    @GET("users/{username}")
    Observable<User> getUser(@Path("username") String username);
}Copy the code

To initialize the Retrofit, we use the LoggingInterceptor to print the returned data.

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(); }}Copy the code

Test code:

@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() {}
                });
    }
}
Copy the code

In the above code, because the network request is asynchronous, we can’t directly test the data, so we can’t print out the Log and test. So we use the initRxJava2() method to turn asynchronous to synchronous. So we can see the return information. The test results are as follows:

The above example is simple and intuitive, so the method of the request interface is written in the test class. In practice, we can call the request method directly from the class where the Mock method resides. Test the state of View control with Robolectric.

If you find it cumbersome to add initRxJava2 to every test, you can either abstract it out, or use @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(); }}; }}Copy the code

2. Simulation data

1. Simulate data using interceptors

Simulate the response data using okHTTP’s interceptors.

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; }}Copy the code

Test code:

@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; OkHttpClient = new okHttpClient.builder ().addInterceptor(new LoggingInterceptor()) .addinterceptor (new MockInterceptor("json data "))//<-- addInterceptor.build(); // Set HttpClient 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 the Exception {mockGithubService. GetUser (" weilu ") / / < - was introduced into the wrong user name here .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<User>() { @Override Public void onSubscribe(Disposable d) {} @override public void onNext(User User) {assertEquals(" User ", 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() {} }); }}Copy the code

We passed in the wrong user name, but the simulated response was preconfigured, so the test result was unchanged.

With this in mind, we can modify the MockInterceptor code to simulate the 404 case.

2.MockWebServer

MockWebServer is a Square produced library that is distributed along with OKHTTP to Mock server behavior. Here’s what MockWebServer does for us:

  • You can set the HTTP response header, body, status code, and so on.
  • You can record the received request and get the body, header, method, path, and HTTP version of the request.
  • It can simulate slow network environment.
  • Provide a Dispatcher that allows the mockWebServer to give different feedback based on different requests.

Add a dependency:

TestCompile 'com. Squareup. Okhttp3: mockwebserver: 3.9.1'Copy the code

Test code:

@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; // Create a MockWebServer server = new MockWebServer(); MockResponse MockResponse = new MockResponse().addHeader(" content-type ", "application/json; charset=utf-8") .addHeader("Cache-Control", "no-cache") .setBody("{\"id\": 12456431, " + " \"name\": Only deer \ "\", "+" \ \ "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).setbody ("{\"error\": \" network exception \"}"); server.enqueue(mockResponse); // Respond successfully to server.enqueue(mockResponse1); OkHttpClient = new okHttpClient.builder ().addInterceptor(new LoggingInterceptor()).build(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://" + server.getHostName() + ":" + server.getPort() + "/") / / setting corresponds to the Host and port number. The client (okHttpClient). AddConverterFactory (GsonConverterFactory. The create ()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); mockGithubService = retrofit.create(GithubApi.class); } @ Test public void getUserTest () throws the Exception {/ / request the same 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 ", 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() { } }); // Verify that our request client generated the request as expected RecordedRequest Request = server.takerequest (); AssertEquals (" GET/users/simplezhli HTTP / 1.1 ", request. GetRequestLine ()); AssertEquals (" okhttp / 3.9.1, "request. GetHeader (" the user-agent")); Server.shutdown (); }}Copy the code

The comments in the code are clear, so there is no need to explain too much, and it is very simple to use.

Execution result (successful) :

Failure result:

By default, MockWebServer presets responses that are first-in, first-out. This may limit your testing, which can be handled by the Dispatcher, such as selecting the forwarding by the path of the request.

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\": Only deer \ "\", "+" \ \ "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).setBody("{\"error\": \" network exception \"}"); }}}; server.setDispatcher(dispatcher); / / set the DispatcherCopy the code

Through the above example, is it a good solution to your pain point, hope to help you. As long as we have the development documents with the background and agree on the data format and field names, we can do our development more agile. All of this code has been uploaded to Github. Hope you can give more support!

Reference 3.