Syntax highlighting powered by Splash

Often when we want to test code that depends on external resources such as a backend or a local database, we need to mock those dependencies. Mocking usually consists of defining a protocol which our other objects depend on rather than depending on a concrete implementation, as well as creating at least two classes/structs that implement that protocol - one that’s used in our app, and at least one mock version used for testing.

This choice of path usually ends up in writing lots of boilerplate code, in addition to all the extra code we’re already writing for our tests. Few people would probably argue against testing as a good thing, but in the harsh reality of tight deadlines it’s no secret that test code is constantly in harm’s way. So, anything we can do to make writing tests less of a hassle, we should.

In this article we’re going to a explore a less object-oriented approach, in which we’ll mock methods rather than entire objects.

Fetching movies

Let’s say we have a simple app that fetches a bunch of movies from an api and presents them in a MovieListViewController. To do this, we need some sort of APIService that makes network requests to fetch that data.

Since we want to write tests for objects depending on this class, we want to be able to mock it, so we start by defining a protocol as follows

protocol NetworkFetching {
    func fetchMovies(result: (Result<Movie>) -> Void)
}

Then we create the concrete implementation that will be used used in our app…

struct APIService: NetworkFetching {
    func fetchMovies(result: (Result<Movie>) -> Void) {
        //Perform network request to fetch movies
        ...
    }
}

… and then our mocked class.

struct MockAPIService: NetworkFetching {
    func fetchMovies(result: (Result<Movie>) -> Void) {
        //Return a hard-coded result
        ...
    }
}

We’ve designed our app so that MovieListViewController has a MovieListViewModel, which in turn has a reference to an APIService instance implementing the NetworkFetching protocol. The view model also has a state property which our view controller binds to in order to update it’s views. We might, for example, want to show/hide a loading indicator as we’re waiting for/presenting data, and show error messages if the request should fail.

class MovieListViewModel {
    private lazy var apiService = factory.makeAPIService()
    ...
    
    func fetchMovies() {
        state = .loading
        apiService.fetchMovies { result in
            switch result {
            case .success(let movies):
                self.movies = movies
                state = .doneLoading
            case .failure(let error):
                state = .failedLoading
            }
        }
    }
}

We now decide we want a unit test that ensures our view model always correctly updates it’s state property to .doneLoading when we get a successful reponse from our backend. In other words, we want to test our view model’s fetchMovies method but mock out the actual network request in our APIService. We also want to make sure our view model’s movies property is updated as expected. So we’ll implement the mocked method to just return hard-coded data

struct MockAPIService: NetworkFetching {
    func fetchMovies(handler: (Result<Movie>) -> Void) {
        let movies = [
            Movie(title: "Forrest Gump", rating: 5),
        ]

        handler(.success(movies))   
    }
}

Then, we’ll write our test

func testFetchMoviesSuccessShouldSetStateToDoneLoading {
    let viewModel = MovieListViewModel(factory: MockFactory())

    viewModel.fetchMovies()
    XCTAssertTrue(viewModel.state == .doneLoading)
    XCTAssertTrue(viewModel.movies.count == 1)
}

One thing to note is that we’re using a Factory to set up our objects and inject that Factory rather than making our objects responsible for injecting and creating other objects. This pattern is really great for isolating dependencies, but by using this approach together with mocking, we probably also want to create a MockFactory that implements a Factory protocol. In other words, even more boilerplate code!

“Just-in-time” mocking

Perhaps it’s time we stop and think about what part of all that extra code that was actually needed for us in order to test our code. The MockAPIService class has no real value in itself rather than being a wrapper for the mocked fetchMovies method whose output - a result type containing a hard-coded list of movies - we were interested in. And although the protocol-oriented nature of Swift is really great, it’s not unusual that we end up with protocols that are there just for the sake of mocking.

Perhaps we could try a more functional approach! So let’s redo this a bit. Firstly, we’ll scrap our MockAPIService as well as the NetworkFetching protocol. Also, away with the unnecessary MockFactory implementation.

We’ll then rewrite our ApiService from this…

struct APIService: NetworkFetching {
    func fetchMovies(result: (Result<Movie>) -> Void) {
        ...
    }
}

… to this

struct APIService {
    var fetchMovies: ((Result<Movie>) -> Void) -> Void = { handler in
        ...
    }
}

Our fetchMovies method still has the exact same function signature as it did before…

((Result<Movie>) -> Void) -> Void

… but we redefined it using closure syntax and stored it in a variable, the latter being key since mutability is just what we’re after here.

Now that we’ve scrapped 2 protocols and 2 mocked classes, we should try to rewrite our unit test.

func testFetchMoviesSuccessShouldSetStateToDoneLoading {
    var viewModel = MovieListViewModel(factory: Factory())

    viewModel.apiService.fetchMovies = { handler in
        let movies = [
            Movie(title: "Forrest Gump", rating: 5),
        ]

        handler(.success(movies))   
    }

    viewModel.fetchMovies()
    XCTAssertTrue(viewModel.state == .doneLoading)
    XCTAssertTrue(viewModel.movies.count == 1)
}

Notice that by just swapping a method implementation, we could easily mock the state we needed to test our code. Since we removed the mock classes and also the protocols that were really just there for testing purposes, all our test-specific code is right now contained in our test target, right where it belongs!

One great thing about this approach is it’s flexibility. For example, we might want to mock several different types of network responses to test how our depending classes behave. This is hassle-free and involves no creation of additional mock objects

func testFetchMoviesErrorShouldSetStateToFailedLoading {
    ...
    viewModel.apiService.fetchMovies = { handler in
        handler(.failure(.timeout))   
    }

    viewModel.fetchMovies()
    XCTAssertTrue(viewModel.state == .failedLoading)
}

Drawbacks

Ok, so here come the boring, mildly shocking, news: This solution is not perfect 😲. Let’s look at some of the drawbacks.

  • Named parameters don’t work with closures

Named parameters is, according to me, one of the things that makes Swift really stand out from many other languages. Unfortunately, defining our methods as mutable closures, we have to give up on this great feature

//this
func set(_ movie: Movie, asFavorite favorite: Bool) {...}

set(someMovie, asFavorite: true)

//becomes this
var set: (Movie, Bool) = { movie, favorite in ...}

set(someMovie, true)
  • Mutability

It might seem as a bit of an anti-pattern to have all this mutable state floating around in our application. Actually, as far as methods are concerned, the risk of an object accidentally replacing a method implementation with another one is probably not that big.

Making stored properties mutable for the sake of testing flexibility, however, seems far riskier and is probably something we should avoid doing. Using computed properties or immutable stored properties as much as possible should be the way forward here.

Thanks a bunch for reading! Please feel free to reach out on Twitter and share your thoughts on this and other Swifty topics!