Stubs are mocks with injected behavior. At the same time they’re used to capture interaction and verify expectations, stubs can simulate different behaviors of the external entity.

A good example is the interaction with a network client when testing a view model or a repository. The subject under test is the view model or repository, and the network client is an external entity. When testing the view model, no network calls should be made. At the same time we want tests to be deterministic. The same input should always produce an expected result.

When testing the view model, we also want to cover different situations the network client might return. For instance, success and failure, and depending on the result, the subject under test should behave differently.

For that reason it’s import to inject behavior on the network client mock. By doing that, we’re creating a stub, given we are stubbing the mock.

The ArchiverProtocol below defines the contract of an archiver which has two methods: one to archive an object and another one to retrieve it.

protocol ArchiverProtocol {
    typealias Content = Encodable
    func archive(_ object: Content)
    func unarchive() -> Content?
}

The most basic mock implementation for the ArchiverProtocol can be seen below. The method unarchive() returns nil just because the interface requires something to return.

class ArchiverMock: ArchiverProtocol {
    private(set) var lastArchivedObject: Content?
    private var stubbedUnarchiveReturn: Content?

    func archive(_ object: Content) {
        lastArchivedObject = object
    }

    func unarchive() -> Content? {
        nil
    }
}

The stub, on the other hand, introduces behavior to a mock. Below it’s possible to control the return value of the unarchive() method, which will allow us to control the interaction of the archiver with the code which consumes it.

class ArchiverMock: ArchiverProtocol {
    private(set) var lastArchivedObject: Content?
    private var stubbedUnarchiveReturn: Content?

    func archive(_ object: Content) {
        lastArchivedObject = object
    }

    func unarchive() -> Content? {
        stubbedUnarchiveReturn
    }
}

extension ArchiverMock {
    func stubUnarchive(toReturn value: Content) {
        stubbedUnarchiveReturn = value
    }
}

Taking as an example a view model, ViewModel, which depends on the archiver to display data and respond to the user interaction:

struct ViewModel {
    let archiver: ArchiverProtocol

    var title: String? {
        archiver.unarchive() as? String
    }

    func didSelect(_ value: String) {
        archiver.archive(value)
    }
}

In the code above, there’s no reference to the archiver real implementation. There’s no information whether the archiver is reading and writing in disk as a file, using CoreData, or any other way to persist information. And that’s the main reason to mock and stub the archiver, since the subject under test is the view model.

The test below reflects the case where the archiver doesn’t have any stored data. In this case, the view title is nil.

func testTitleWhenArchiveIsEmpty() {
    let archiverMock = ArchiverMock()
    let viewModel = ViewModel(archiver: archiverMock)

    XCTAssertNil(
        viewModel.title,
        "It shouldn't have a title when there's nothing archived"
    )
}

On the other hand, when the mock is stubbed to return a value, the value from the title property should match the stubbed title from the archiver.

func testTitleWhenArchiveHasValue() {
    // Given

    let archiverMock = ArchiverMock()
    archiverMock.stubUnarchive(toReturn: "Some Title")

    // When

    let viewModel = ViewModel(archiver: archiverMock)

    // Then
    
    XCTAssertEqual(
        viewModel.title,
        "Some Title",
        "It should return the archived title"
    )
}

In other words, stubs allow us to inject behavior to a mock so that we have full control of the interactions of the system under test and external dependencies, no matter how these external dependencies might behave; allowing us to simulate the different behaviors the external dependency might have.


Where to go from here:

  1. Introduction
  2. The Different Types of Tests
  3. Test Doubles
    1. Dummies
    2. Mocks
    3. Stubs
    4. Partials
    5. Fakes
  4. Fixtures
  5. Frameworks and Tools