mockwebserver +

Additional

Language
Java
Version
N/A
Created
Mar 31, 2016
Updated
Jun 13, 2017
Owner
Orhan Obut (orhanobut)
Contributors
Orhan Obut (orhanobut)
oguzbabaoglu
2
Activity
Badge
Generate
Download
Source code
APK file

Show card

mockwebserver +

Issue

MockWebServer is a great tool for mocking network requests/responses. In order to add response, you need to set MockResponse body along with all properties you need

@Rule public MockWebServer server = new MockWebServer();

@Test public void uglyTest() {
  server.enqueue(new MockResponse()
    .setStatusCode(200)
    .setBody({
               "array": [
                 1,
                 2,
                 3
               ],
               "boolean": true,
               "null": null,
               "number": 123,
               "object": {
                 "a": "b",
                 "c": "d",
                 "e": "f"
               },
               "string": "Hello World"
             })
    .addHeader("HeaderKey:HeaderValue")
    .responseDelay(3, SECONDS)
  );
  
  // execute request
  // assert
  // verify
}

Imagine it with huge json responses. It will obscure the method content and will be barely readable.

Solution

In order to make it more readable, you can use fixtures. Move away your response to the fixtures and just reference them. MockWebServerPlus is a wrapper which contains MockWebServer with fixtures feature.

Create a fixture yaml file under resources/fixtures
src
├── test
│   ├── java
│   ├── resources
│   │   ├── fixtures
│   │   │   ├── foo_success.yaml
│   │   │   ├── foo_failure.yaml
statusCode : 200       // as the name says
delay: 0               // delays the response
headers:               // adds to the response
- 'Auth:auth'
- 'key:value'
body: 'common/body_file.json' // can be any path under /fixtures folder
// or inline
body: >                       // can be any text, json, plain etc. Use > letter for scalar text
    {
      "array": [
        1,
        2,
        3
      ],
      "boolean": true,
      "null": null,
      "number": 123,
      "object": {
        "a": "b",
        "c": "d",
        "e": "f"
      },
      "string": "Hello World"
    }

Use the file name to reference it. That's it!

@Rule public MockWebServerPlus server = new MockWebServerPlus();

@Test public void readableTest() {
  server.enqueue("foo_success");
  
  // execute request
  // assert
  // verify
}

Use the generated Fixtures.java to reference your fixtures. Read the Generate Fixtures.java part

server.enqueue(Fixtures.FOO_SUCCESS);

Enqueue multiple response

server.enqueue(Fixtures.FOO_SUCCESS, Fixtures.USER_REGISTER_SUCCESS);

Custom Dispatcher

You may wish to use a custom dispatcher with the mock web server.

Fixtures can be used directly inside a Dispatcher:

new Dispatcher() {
  @Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
    return Fixture.parseFrom("simple).toMockResponse();
  }
}

Generate Fixtures.java

You can always use plain text to reference your fixtures.

server.enqueue("foo_success");

but you can also generate Fixtures.java file to have all of them with a task. This will make your code more type-safe. Put the following task into your build.gradle file and execute it when you add/modify your fixture resources.

task generateFixtures(dependsOn: copyTestResources) << {
  def directory = projectDir.path + '/src/test/java'
  new File(directory + '/fixtures').mkdir()
  def path = directory + "/fixtures/Fixtures.java"

  def builder = '' << ''
  builder.append("package fixtures;\n\n")
  builder.append("public class Fixtures {\n\n")
  builder.append("  private Fixtures() {\n")
  builder.append("    //no instance\n")
  builder.append("  }\n\n")

  def resources = android.sourceSets.test.resources.srcDirs.getAt(0)
  if (resources.size() > 0) {
    resources.eachDirMatch("fixtures") { dir ->
      def fixturesFile = dir
      fixturesFile.eachFile(FileType.FILES) { file ->
        if (file.name.endsWith(".yaml")) {
          String fileName = file.name.split('\\.')[0]
          builder.append("  public static final String ")
              .append(fileName.toUpperCase())
              .append(" = ")
              .append('\"')
              .append(fileName)
              .append('\";\n')
        }
      }
    }
  }
  builder.append("}\n")

  new File(path).write(builder.toString())
}

Above solution will generate Fixtures.java when you execute it manually. But you might forget to execute it, you can make it dependent for any task to make it automated. Whenever preBuild is executed, it will also execute this task

preBuild.dependsOn generateFixtures

Install

testCompile 'com.orhanobut:mockwebserverplus:2.0.0'

// This is optional, but in order to be up-to-date with OkHttp changes, you can use the latest version
testCompile 'com.squareup.okhttp3:mockwebserver:3.7.0'  

Other proxy methods

MockWebServerPlus.server()         // returns MockWebServer instance
MockWebServerPlus.takeRequest()    // returns RecordedRequest
MockWebServerPlus.url(String path) // returns url to execute
MockWebServerPlus.setDispatcher(Dispatcher dispatcher)  // any custom dispatcher
MockWebServerPlus.enqueue(SocketPolicy socketPolicy)    // Useful for network errors, such as DISCONNECT etc

Get the fixture object

Fixture fixture = Fixture.parseFrom(Fixtures.SIMPLE);

For non-android modules

For non-android modules, you may need to add the following tasks to copy your resources into classes dir

task copyTestResources(type: Copy) {
  from sourceSets.test.resources
  into sourceSets.test.output.classesDir
}

How it works

Also notice that accessing sourceSets should be without android.

Credits

MockWebServer from Square

License

Copyright 2017 Orhan Obut

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.