
Mock network layer for unit testing in Swift
Mock network layer
Let’s say we have an interface for downloading data from server. Let’s call it AppServerClient. AppServerClient has a method called getFriends. It returns a list of friends if network request is successful and there is some data stored in the web service. So we need to mock the AppServerClient for testing purposes. We can do this by subclassing the original service:
1 2 3 4 5 6 7 |
private final class MockAppServerClient: AppServerClient { var getFriendsResult: AppServerClient.GetFriendsResult? override func getFriends(completion: @escaping AppServerClient.GetFriendsCompletion) { completion(getFriendsResult!) } } |
Mock network layer success case
AppServerClient uses Result to communicate the response to who ever is calling it. Result is an enum that contains 2 cases: success(payload:T) & [rad-hl]failure(U?).
1 2 3 4 |
enum Result<T, U> where U: Error { case success(payload: T) case failure(U?) } |
1 2 3 4 5 6 7 8 9 10 11 12 |
func testSuccess() { let appServerClient = MockAppServerClient() appServerClient.getFriendsResult = .success(payload: []) let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) viewModel.getFriends() guard case .some(.empty) = viewModel.friendCells.value.first else { XCTFail() return } } |
We created a test called testSuccess. First, we need to create an instance of the MockAppServerClient. Then we’ll set the getFriendResult to fit the expected result of our test case. For the sake of simplicity the getFriendResult now returns an empty array. Next, we’ll create an instance of the view model and pass AppServerClient as a parameter.
Here, we use dependency injection to make sure ViewModel is using the mock instance of AppServerClient. When testing against the mock network layer, we need to know how the module using it actually works. After receiving the friends, ViewModel tries to create tableViewCells from the response. Since the getFriendResult is set to an empty array, we know there is no cells available. We can check that by using the guard case statement and make sure the binded cell type is empty. If that is not the case XCTFail() marks the test as a failure.
Now, let’s test for a failing case
Mock network layer failing case
We map network request failures to human readable format in AppServerClient.
1 2 3 4 |
enum GetFriendsFailureReason: Int, Error { case unAuthorized = 401 case notFound = 404 } |
So this is what we will set for the response in the case of a failing request. Here is the complete failure test case called testFailed:
1 2 3 4 5 6 7 8 9 10 11 12 |
func testFailed() { let appServerClient = MockAppServerClient() appServerClient.getFriendsResult = .failure(AppServerClient.GetFriendsFailureReason.notFound) let viewModel = FriendsTableViewViewModel(appServerClient: appServerClient) viewModel.getFriends() guard case .some(.error(_)) = viewModel.friendCells.value.first else { XCTFail() return } } |
Again, let’s create an instance of MockAppServerClient. This time the result is set to failure[rad-hl]. The value inside the failure case is an error of type notFound. We defined this in the [rad-hl]GetFriendFailureReason above. Next, we will do the same things as in the successful case. Only difference here is, this time the cells created contains an error cell. If that is not the case XCTFail() will again mark the test as a failure.
Conclusion
Mocking network layer is a crucial technique to set up your safety net by unit testing. It is not all that hard, but for a long time I had an image in my head of it being difficult. Since you and I now know that is not the case, I hope that you’ll also start using it. I encourage you to check out the more thorough article about unit testing viewmodel in Swift. That article goes a bit deeper into the world of unit testing.
As usual if you have any question, leave a comment, email or ping me on twitter! Thank you for reading and have a great day my friend!