Adventures in iOS Development
Unit Testing and Clean Code Exploration Toolbox Archive About Feed

HTTP testing in Swift with Nocilla

In this post you’ll learn about:

  • testing networking layer with Nocilla and XCTest,
  • using Objective-C libraries with Swift,
  • new XCTest asynchronous testing API,
  • CocoaPods,
  • basics of test naming and structure.

Testing networking code

There are a few problems with unit testing the networking layer:

  • Results are not 100% reliable (tests may fail due to a number of factors - bad connection, server downtime, etc.).
  • Test are slow - any external dependencies (networking, disk access, etc.) cause tests to be slower, than when they would run solely in-memory.
  • It may be also hard to setup and test specific conditions, especially if you want to check something other than the “happy path” (“Does this error handling code works like it’s supposed to?”).

Solution to this is to stub networking, to mitigate the need to hit the network or having a working server. Nocilla is a library that stubs HTTP requests and responses. It has great API with custom DSL that allows to fluently compose the stubs. It’s written in Objective-C, but due to Swift - Objective-C interoperability it’s possible to use it for tests in Swift projects. Easiest way to integrate a third party dependency in a project is with CocoaPods.

Installation Guide

As usage instructions on Nocilla’s github are written for Kiwi framework and for Objective-C, I decided to write a guide on how to set it up in Swift project and use with vanilla XCTest framework. Steps:

  • Install CocoaPods.
  • In projects top level folder execute command: pod init
    • It will create a Podfile. Edit it with your text editor of choice and add Nocilla to your test target. In my case test target is named ServiceTests and Podfile looks like this:
target 'ServicesTests' do
  pod 'Nocilla'
end
  • To install CocoaPods dependencies call pod update. After this projects have to be run from newly created (project name).xcworkspace.
  • To use Objective-C code in Swift a bridging header is required. For any libraries you’d like to be visible in Swift add imports to this header.
    • Simplest way of setting up bridging header is to add an empty Objective-C file to the target (New File -> Source). Xcode will ask you if you want a bridging header. Sure you do! After this you delete the just added empty file. This helps to avoid creating header manually (and potentially messing up paths).
    • Add #import "Nocilla.h" to generated header - now it’s accesible in Swift.

Test suite setup and testing asynchronous code

I’ve created NocillaTestCase (gist deriving from XCTestCase, to encapsulate all the setup that’s needed to be done when testing with Nocilla. Class methods setUp and tearDown make sure the library is doing the stubbing only during this test suit:

override class func setUp() {
    super.setUp()
    LSNocilla.sharedInstance().start()
}

override class func tearDown() {
    super.tearDown()
    LSNocilla.sharedInstance().stop()
}

Stubs get cleared between tests of the suite.

override func tearDown() {
    ...
    LSNocilla.sharedInstance().clearStubs()
}

Due to the fact that “NSURLSession API is highly asynchronous” we need to use the new XCTest API and setup an expectaction before each test. Expectaction signals that there’s async code to be executed. At the end of the test call waitForExpectationsWithTimeout, so the function won’t return and execute next test. It waits until expectaction is fullfiled (you need to call it manualy in test completion handler) or if it times out. When running tests with Nocilla, timeout can be set to very low values (under 0.1). Even if it fails it’s still faster than running a network call - response is almost immediate. Add it at the end of the test:

waitForExpectationsWithTimeout(0.1, handler: nil)

Passing nil to handler is enough for tests to fail in case of timeout. If you need to do any additional work in handler, be sure to check if handlers NSError parameter is non nil and call XCTFail assertion, as handler is invoked both on expectaction fullfilment and timeout.

As each test in a suite will use asynchronous API, NocillaTestCase creates new expectaction in setUp instance method and clean it up in the tearDown. Note: to avoid creating initializer 1 expectaction is declared as an explicitly unwrapped XCTestExpectation!.

Testing HTTP requests with Nocilla

After this whole setup (which more or less you’ll need to do only once) we’re ready to write first tests! I’ll use Nocilla to test WebService class I created to handle JSON services. First test will check if completion handler is passed right data when server responds with a proper JSON.

func test_fetchParameters_200ProperJSON_CallsCompletionHandlerWithJSONObject() {
    // arrange
    let service = makeJSONWebService(baseURLString: baseURLString, defaultParameters: emptyParameters)
    stubRequest("GET", baseURLString).andReturn(200).withBody("{\"ok\":1}")
    // act
    service.fetch(emptyParameters) { result in
        self.expectation.fulfill()

        // assert
        XCTAssertEqual(["ok": 1], result.data()!)
    }
    waitForExpectationsWithTimeout(0.1, handler: nil)
}

Stubbing is straighforward. Start with stubRequest and define HTTP method and URL with either a string or a regex, then define request/response headers, payload, and response status code. In this example tested unit of work is a web service library fetch call. Scenario/state under test is 200 OK response from server with JSON payload. As a result we’re expecting completion handler to be called with JSON parsed to dictionary. Other scenarios to test could include handling of the 404 response:

stubRequest("GET", baseURLString).andReturn(404)

or testing malformed JSON data reponse:

stubRequest("GET", baseURLString).andReturn(200).withBody("{1}")

There are many more examples of stubbing requests with Nocilla’s elegant DSL on github. Those include even responding with data recorded with curl and failing requests with specific NSError.

Tests Structure and Naming

Notice the Arrange-Act-Assert pattern that helps to keep tests clean. In Arrange part all needed objects/dependencies are set up, then method under test is invoked (Act), and finally we assert results/the state of the system under test (Assert). More details about the pattern here. Another thing worth noting is the omition of the assertion message. I’ve used following standard for naming unit test (more details):

UnitOfWork_StateUnderTest_ExpectedBehavior


  1. Which would be impossible anyway as XCTestCase default initializer takes in NSInvocation, which is "unavailable".

    </li> </ol></div>