Last time, we looked at How Does Swift Support Stubbing and Mocking? Swift’s
extension declaration opens the way, letting us attach new protocols to existing classes. That much is well-known in the Swift community.
But then what? How do we create useful mock objects? I’ve seen many examples that are okay… to start with. But in the long run, I’m afraid they’ll fall short for ongoing maintenance.
I wrote OCMockito because I wanted Objective-C mock objects that struck a better balance between “precise” and “forgiving”. I also wanted test code that was easier to read and write. What can we do with Swift mock objects?
Let’s continue the Swift version of the Marvel Browser TDD sample app. I want the FetchCharactersMarvelService to ask a URLSession for a data task. Here’s where we left the production code:
In the test code, let’s create the outer shell of a URLSession mock object:
That works because the protocol is currently empty. But the protocol needs to declare the slice of URLSession’s interface that we want to use. This actually drives us to apply the Interface Segregation Principle to existing classes!
To get the method we want, control-click on URLSession to reach its interface. We want to request a data task with an URL and a completion handler. Copy and paste the declaration from URLSession to URLSessionProtocol, but delete the
There’s nothing further to do on the production code side. The protocol declares something that URLSession already provides. But now we get an error on the mock object. It needs to implement the method.
As with dynamically-created Objective-C mocks, a Swift mock object can’t diverge from the real object. Not as long as the
extension is empty, which guarantees that the protocol is a subset of the full interface.
Here’s the shell of the mock object:
It does nothing with the arguments. (This will change.) It returns a newly-created data task, to satisfy its requirements. (This will change.) It’s a skeleton on which we will build.
The first thing we want to verify is that calling fetchCharacters on our service will ask URLSession to create a data task. So we’ll have the mock object record whether the method was called. Here’s how most people do this:
Now a test can create the MockURLSession, inject it to the FetchCharactersMarvelService, call fetchCharacters, and confirm the
dataTaskWasCalled is true. It works. But it’s not precise.
It’s probably too forgiving.
We usually want to know more than “Was this method called?” Most of the time, my question is, “Was this method called exactly one time?”
This is (sort of) what OCMockito’s
verify(mockObject) statement does. It’s shorthand for
Let’s get closer to this in our Swift mock object. It’s easy. Change from setting a boolean flag to incrementing a call count:
Here’s a beginning to my first test:
This small change brings greater precision to our tests! Just this past week, a unit test with a precise call count revealed that my code was making an unwanted second call.
But the change also brings greater flexibility. If there were a need, we could have a test confirm, “The method should be called at least once,” or “It should be called no more than twice.”
The test above isn’t finished. Its job is to confirm that the service call “should make data task for Marvel endpoint”. To do that, we need to capture the URL argument.
Since we only need to confirm one call, let’s just capture the last argument:
Now we can access
dataTaskLastURL — that is, the URL argument the last time dataTask was called. The test will use this to check its
This is a good test. But can we make it better still?
An important rule of thumb is that a test should have a single assertion. More accurately, it should “assert one truth”. Our test has two assertions, but they work to verify one truth: “A single data task was requested, with a URL that points to the Marvel API.”
But I’m bothered by the rule of thumb. It leads me to ask, what would it look like to have a helper method verify that one truth?
Most of the time, entire arguments are checked for equality. But that’s not the case for this test. Instead, we want to access a property of the argument. This is more obvious in the Objective-C version, using OCMockito’s hasProperty matcher:
How can we approach this in Swift? Putting on my OCMockito hat, I realize that in most cases, we verify most arguments using an implicit
equalTo matcher. A matcher is:
Let’s go with the most essential piece, which is the predicate. We want to evaluate an optional URL. So the signature of the predicate is
(URL?) -> Bool. Let’s pass the predicate to a method in MockURLSession:
Our test can use this new verify method, passing a closure that checks the URL’s
Do you see what’s great about this verify method? Because the test specifies a predicate, verifyDataTask can be used for all sorts of tests!
The verify method works well in the success case. But the error reporting needs improvement. For starters, we now have a helper with calls to multiple XCTest assertions. To distinguish between them, let’s add a message to the first assertion:
That’s better. How about the second assertion?
The first assertion is an XCTAssertEqual. If there’s a discrepancy, it will report both values. So we’ll know what the actual call count is. But the second test is an XCTAssertTrue. If it fails, it won’t tell us why. Let’s get more information by reporting the actual URL:
One last thing. When the verification fails, what line number is reported? Unless we take extra steps, the line number will point to the assertions in the helper method. But we want the line number in the test code, not the helper.
I’ve already shared how to fix this in How to Make Specialized Test Assertions in Swift. We’ll add file name and line number arguments to the helper. It then passes those arguments to the underlying assertions:
We now have a very useful mock object!
Our new verifyDataTask method is really helpful. But the semantics are still different from OCMockito. In our Swift code, we check that there’s exactly one call. And we check that the argument satisfies a given predicate.
OCMockito does things differently. It records all invocations to a method. When it’s time to verify calls, it checks to see how many of those calls satisfy its argument matchers.
In other words, the OCMockito version won’t mind if there are other requests for data tasks. It’s just checking that there’s only one to the Marvel API.
This subtle difference makes the OCMockito version less fragile. It’s more accommodating to future changes in the code.
This gives me a clue for how we might make Swift mock objects even more flexible going forward. …See my try! Swift Tokyo talk for more.
For now, here are my 6 recommendations for creating Swift mock objects:
linearguments, passing them to the underlying assertions.
Questions, comments, concerns? Please share your thoughts below!TDD Sample App: The Complete Collection …So Far]
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.