mokacoding

unit and acceptance testing, automation, productivity

Getting Started With OHHTTPStubs

In the previous post I tried to show the importance and the benefits of decoupling your unit tests from the network, and suggested ways to achieve that. In this new post of the Practical Testing in Swift series we'll look at a concrete implementation.

In short, stubbing network requests in your unit tests will provide you with faster and more reliable tests, make them deterministic, and allow more testing of error and edge cases.

One option to remove the dependency from the network is to use a network stubbing library, and among those OHHTTPStubs is one the most elegant.

This library developed by Olivier Halligon allows you to specify criteria on which to match URL requests leaving the app, and hijack them providing a custom response.

You can integrate OHHTTPStubs in your test target in the usual ways, CocoaPods, Carthage, or manually. Let me know if you need help with the process.

The API

An OHHTTPStubs stub instruction has this structure:

stub(/* your stub criteria */) { request in
  return /* your response */
}

There are two pieces that form the stub instruction, the criteria to use to evaluate whether or not a request made by the app should be hijacked, and the stubbed response to return. For example:

stub(isMethodGET()) { _ in
  return OHHTTPStubsResponse(
    JSONObject: ["key": "value"],
    statusCode: 200,
    headers: [ "Content-Type": "application/json" ]
  )
}

Will intercept any GET request and return a simple JSON object { "key": "value" }. Another example:

stub(isHost("api.myserver.com") && isPath("/resource")) { _ in
    guard let path = OHPathForFile("success_resource_response.json", self.dynamicType) else {
            preconditionFailure("Could not find expected file in test bundle")
    }

    return OHHTTPStubsResponse(
            fileAtPath: path,
            statusCode: 200,
            headers: [ "Content-Type": "application/json" ]
    )
}

Will intercept any request towards api.myserver.com/resource and return the content of the JSON file success_resource_response.json located in the test bundle.

stub(isPath("/foo/bar")) { _ in
    let error = NSError(
        domain: "test",
        code: 42,
        userInfo: [:]
    )
    return OHHTTPStubsResponse(error: error)
}

Will stub any request with path "/foo/bar" and make it fail returning an NSError with domain test and code 42.

This last request is very useful when testing that our network logic handles failures properly. Testing failures is not as simple when interacting with a real server.

You can read more about the possibilities offered by the library in the Usage Examples Wiki page.

In Practice

Let's imagine we have an APIClient service class with a method getResource(withId: String, completion: (resource: Resource?, error: ErrorType?) -> ()) which would go off to the server and fetch the Resource object with the given id. If the request succeeds the completion function will be executed with the serialized Resource and no error, if it fails the resource will be .None, and the error will have the value of the received network or server error. Pretty standard networking code.

Here's a possible test for the behaviour of APIClient when the request succeeds:

func testGetResourceSuccess() {
  // Arrange
  //
  // Setup network stubs
  let testHost = "te.st"
  let id = "42-abc"
  let stubbedJSON = [
    "id": id,
    "foo": "some text",
    "bar": "some other text",
  ]
  stub(isHost(testHost) && isPath("/resources/\(id)")) { _ in
    return OHHTTPStubsResponse(
      JSONObject: stubbedJSON,
      statusCode: 200,
      headers: .None
    )
  }
  // Setup system under test
  let client = APIClient(baseURL: NSURL(string: "http://\(testHost)")!)
  let expectation = self.expectationWithDescription("calls the callback with a resource object")

  // Act
  //
  client.getResource(withId: id) { resource, error in

    // Assert
    //
    XCTAssertNil(error)
    XCTAssertEqual(resource?.id, stubbedJSON["id"])
    XCTAssertEqual(resource?.aProperty, stubbedJSON["foo"])
    XCTAssertEqual(resource?.anotherPropert, stubbedJSON["bar"])

    expectation.fulfill()
  }

  self.waitForExpectationsWithTimeout(0.3, handler: .None)

    // Tear Down
    //
  OHHTTPStubs.removeAllStubs()
}

The test is split in four steps, we start by arranging the inputs for the test and configuring the system under test (sut), we then act and assert that the sut behaved as expected, and finally we tear down any kind of non transient modification we made.

In the arrange step we use OHHTTPStubs to hijack any network request to the resource API endpoint, and return a dictionary that we previously initialized.

In the assert step we make sure of two things: first that APIClient actually runs the completion function passing a non nil resource object, and a nil error; second that the receive Resource is built using the data from the network.

Finally in the tear down step we make sure that all the stubs set in this test are removed, so that the next tests will find a pristine environment.

Manually building the dictionary for the stubbed response in the test is handy because it makes clearer where the values that we expect our Resource to be initialized with came from, but it might be cumbersome if the properties are more than a handful.

In such cases I prefer using a .json file as the stubbed response, thanks to the OHHTTPStubsResponse(fileAtPath:, statusCode:, headers:) method we saw before. This approach adds a mental step that the reader of the test has to make when looking at the assertions on the state of the received object, but greatly improves the maintainability of the tests.

Other good side effects of this approach are that the same .json can be used multiple times or in multiple tests, and that if you download your JSON stub files from the server you have a test case that is closer to the "real life".

I will let it up to you to write the failure test using OHHTTPStubs. Or you can check out the example project to see how I would write it.


I hope to have convinced you of the importance of decoupling yourself from the network in your tests, and showed you how simple it is to do so using OHHTTPStubs.

OHHTTPStubs is a great library with a lot to offer, and very well documented, you should really try it.

If you need help with your network stubs, or want to talk more about how to stub the network in your tests get in touch on Twitter or leave a comment below.

On other news: I started a podcast, is called TIL and I share daily(ish) tips on testing, automation, and general software development. I would really appreciate feedback on it, so please head over to https://www.briefs.fm/til an let me know what you think.

Vote on Hacker News