Quality Coding
Shares

Have You Wished You Could Fake a Return Value in Swift?

Shares

I’ve shown you how to replace real objects with fakes in Swift, so that you can begin creating useful mock objects. We did this by moving away from a concrete type to a protocol. Anything that implements the protocol will work, so fakes are no problem.

But what do we do when we can’t change the signature? How do we substitute fake arguments, or a fake return value?

It’s time to talk about partial mocks in Swift.

Motivation: Stub returning a mock

In our TDD sample app, we’re making a Service object that calls the Marvel Comics API. We’ve TDD’d constructing the NSURLSessionDataTask from the request model. And we generate the authentication parameters by injecting a closure.

The next thing is to get the data task to start, by sending it a resume message. Here are the steps in the test:

  1. Create a mock NSURLSessionDataTask.
  2. Stub the call to NSURLSession to return this mock data task.
  3. Exercise the call to the System Under Test.
  4. Confirm that resume was sent to the mock data task.

In Objective-C where mocking is easy, it looks like this using OCMockito:

- (void)testFetchCharacters_ShouldStartDataTask
{
    NSURLSessionDataTask *mockDataTask = mock([NSURLSessionDataTask class]);
    [given([mockSession dataTaskWithURL:anything() completionHandler:anything()])
            willReturn:mockDataTask];

    [sut fetchCharactersWithRequestModel:[self dummyRequestModel]];

    [verify(mockDataTask) resume];
}

The mockDataTask isn’t actually an NSURLSessionDataTask. But Objective-C doesn’t mind, as long as it responds to the same messages.

Swift insists on the correct type

But Swift won’t let us pass an arbitrary fake object. The return type of the URLSession method is a URLSessionDataTask. If we owned the method, we could change the return type to a protocol. But Swift insists on it being a URLSessionDataTask.

Thankfully, URLSessionDataTask is declared as an open class. This means we can subclass it. Then we’ll override the method we care about:

class MockURLSessionDataTask: URLSessionDataTask {
    private var resumeCallCount = 0

    override func resume() {
        resumeCallCount += 1
    }

    func verifyResume(file: StaticString = #file, line: UInt = #line) {
        XCTAssertEqual(resumeCallCount, 1, "call count", file: file, line: line)
    }
}

We also need to modify our MockURLSession. Let’s parameterize the return value by turning it into a property:

    var dataTaskReturnValue: URLSessionDataTask!
    private var dataTaskCallCount = 0
    private var dataTaskLastURL: URL?

    func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask {
        dataTaskCallCount += 1
        dataTaskLastURL = url
        return dataTaskReturnValue;
    }

With these tools in place, we can create the test:

    func testFetchCharacters_ShouldStartDataTask() {
        let mockDataTask = MockURLSessionDataTask()
        mockURLSession.dataTaskReturnValue = mockDataTask

        sut.fetchCharacters(requestModel: dummyRequestModel())

        mockDataTask.verifyResume()
    }

The code to pass this test actually blows up the other tests, because it now calls the force-unwrapped dataTaskReturnValue. Let’s fix this by moving the creation and insertion of the mockDataTask into the common test fixture. Swift has led us to do the correct thing here, because we don’t want any actual calls to resume in our unit tests.

Our test is now simply:

    func testFetchCharacters_ShouldStartDataTask() {
        sut.fetchCharacters(requestModel: dummyRequestModel())

        mockDataTask.verifyResume()
    }

Avoid that partial mock — except when you can’t

MockURLSessionDataTask is a “partial mock” because we’re taking a real class and replacing one method.

Under other circumstances, I’d consider this to be a code smell. Here’s the problem: A partial mock mixes test code with production code in a single object.

But Swift gives us no choice. “Subclass and override method” is a classic technique for working around legacy code. It pains me to use this method on new code, but we must have tests.

Here are my evolving rules around mocks in Swift:

  • Avoid mocks, except when they’re necessary.
  • If you can change the signature, use a protocol-based fake.
  • If you can’t change the signature, use a partial mock.

What if you can’t subclass the type to create a partial mock? You may need to call that section of code “un-unit testable”. Shed a tear, and retreat to the next available test seam. You may be able to find other ways of automating tests for that section of code. Probably not unit tests, though.

Let’s hope we don’t have to retreat too often!

Do you have any questions, concerns or remarks? Please leave a comment below.

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He's been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:

Leave a Comment:

3 comments
Add Your Reply