Mocking in Swift with Cuckoo

Swift is different from other languages. The same rules do not apply. Other languages can use reflection to alter your runtime code to mock out the classes. But you can’t do that in Swift. It’s been designed to be a much safer language and doesn’t allow the code to be modified at runtime. There aren’t any predominant Mocking frameworks such as OCMock in Objective-C or Mockito in Java. So you’re going to have think a little differently to achieve the same results as say OCMock.

The language is also so new and changing so rapidly that as yet there isn’t a stable, mature mocking framework that you can rely on to be updated for Swift 3 let alone future versions of Swift. However there is Cuckoo, which isn’t as neat as OCMock - it uses a two stage process - but looks very promising if you want to add mocking to your tests.

Mocking HTTP

Unit tests should be isolated from any network calls. We want whatever our code is doing with the URL or REST API to be hidden from any network issues when we are unit testing our code. We can test any external interactions later when we’re doing integration testing.

Mocking can be confusing but it can be summarized by the following code

when(methodIsCalled).thenReturn(aValue);

When a methodIsCalled, then always return aValue, for example when you call getWeatherForCity("Troy") then always return 72 or if you call getDateAndTime() then always return {"time": "12:43:12 PM","date": "05-30-2016"}.

The concept is that the temperature or time are then used to test your target methods. You can always rely on it being what you hard coded so you can spend real time trying to break your Fahrenheit to Celsius conversion or your JSON parsers. You can also mock edge cases, such as impossible temperatures (-460) or different time zones (GMT) to see how your code handles it.

There are three parts to creating this mocking behavior
- Arrange - setup the testing objects
- Act - perform the actual test
- Assert - verify the result

Let’s look at a simple HTTP call and then see how we can use Cuckoo to mock it. Listing 1 shows the UrlSession class code that does a HTTP GET for a given URL.

class UrlSession
{
  var url:URL?
  var session:URLSession?
  var apiUrl:String?

  func getSourceUrl(apiUrl:String) -> URL {
    url = URL(string:apiUrl)
    return url!
  }

  func callApi(url:URL) -> String {
    session = URLSession()
    var outputdata:String = ""
    let task = session?.dataTask(with: url as URL) { (data, _, _) -> Void in
        if let data = data {
            outputdata = String(data: data, encoding: String.Encoding.utf8)!
            print(outputdata)    
        }
    }
    task?.resume()
    return outputdata
  }
}

Listing 1: UrlSession.swift code

Ideally we would want to mock this out so we get a specific string of canned HTML or JSON text that we can then manipulate, parse or deconstruct in our application and test how our internal functions work.

In the example we’re using a framework called Cuckoo, which is available from https://github.com/SwiftKit/Cuckoo. It gets around the read-only binary restrictions by using a two stage process. The first stage generates the mocking code from your test objects which is then recompiled in the second stage so you can fake your object calls.

To install Cuckoo take the following steps:-

  1. Create your UrlWithCuckoo project with UrlSession.swift Model class and test class
  2. Install the Cuckoo Pod by first running pod init from the command line
  3. Edit the generated Podfile and add Cuckoo as a test target
target 'UrlWithCuckooTests' do
  pod 'Cuckoo',
  :git => 'https://github.com/SwiftKit/Cuckoo.git',
  :branch => 'master'
end
  1. Run the command pod install
  2. Close the project and reopen the workspace
  3. Click on the Project folder->Test Target (UrlWithCuckooTests)->Build Phases
  4. Click ‘+’ and choose ‘New Run Script Phase’, see Figure 1
  5. Add Listing 2 to the Run Script section
  6. Build the project to create the GeneratorMocks.swift file

Adding a Build Phase
Figure 1: Adding a Build Phase

  # Define output file; change "${PROJECT_NAME}Tests" to your test's root source folder, if it's not the default name
  OUTPUT_FILE="./${PROJECT_NAME}Tests/GeneratedMocks.swift"
  echo "Generated Mocks File = ${OUTPUT_FILE}"

  # Define input directory; change "${PROJECT_NAME}" to your project's root source folder, if it's not the default name
  INPUT_DIR="./${PROJECT_NAME}"
  echo "Mocks Input Directory = ${INPUT_DIR}"

  # Generate mock files; include as many input files as you'd like to create mocks for
  ${PODS_ROOT}/Cuckoo/run generate --testable "${PROJECT_NAME}" \
  --output "${OUTPUT_FILE}" \
  "${INPUT_DIR}/UrlSession.swift"

Listing 2: Cuckoo Run Script

  1. Right click on UrlWithCuckooTests and Add GeneratorModel.swift to the tests folder, see Figure 2.
  2. Using the newly available functions from GeneratorModel.swift we mock out the getSourceURL and callAPI function calls.

Add Generated File to Tests
Figure 2: Add Generated File to Tests

Listing 2 shows the mocked code that returns a simple but valid JSON string. We mock out the two function calls, the first tests that getSourceURL returns a mock URL and the second tests that callAPI returns a simple JSON string.

func testUrl() {
  let mock = MockUrlSession()
  let urlStr  = "http://riis.com"
  let url  = URL(string:urlStr)!

  // Arrange        
  stub(mock) { (mock) in
    when(mock.apiUrl).get.thenReturn(urlStr)
  }

  stub(mock) { (mock) in
    when(mock.url).get.thenReturn(url)
  }

  stub(mock) { (mock) in
    when(mock.session).get.thenReturn(URLSession())
  }
  stub(mock) { (stub) in
    stub.getSourceUrl(apiUrl: urlStr).thenReturn(url)
  }

  stub(mock) { mock in
    mock.callApi(url: equal(to:url, equalWhen: { $0 == $1 })).thenReturn("{'firstName': 'John','lastName': 'Smith'}")
  }

  // Act and Assert
  XCTAssertEqual(mock.apiUrl, urlStr)
  XCTAssertEqual(mock.url?.absoluteString, urlStr)
  XCTAssertNotNil(mock.session)
  XCTAssertEqual(mock.callApi(url: url),"{'firstName': 'John','lastName': 'Smith'}")

  XCTAssertNotNil(verify(mock).session)
  XCTAssertNotNil(verify(mock).apiUrl)
  XCTAssertNotNil(verify(mock).url)
}

Listing 3: testURL() session code

We can now run the test code and the results can be seen in Figure 3.

Test Run
Figure 3: Test Results

We would take these mocks and use them to unit test the remaining code only this time they’re isolated from any http calls. So far other than the 2 stage process and some issues with structs and generics, Cuckoo works similar to what you’re already used to with OCMock. The sample code for this app is up on http://github.com/godfreynolan/cuckoo.

This is Part 5 of a series, the other blogs are below

Part 1 - Swift Unit Testing on Ubuntu
Part 2 - Swift Unit Testing in Xcode
Part 3 - SonarQube, Jenkins and Swift
Part 4 - Swift GUI Testing with XCUI
Part 5 - Mocking in Swift with Cuckoo