Fakes are Test Doubles with real implementation. They’re real objects, but they take shortcuts - or alternative solutions - to deliver their purpose. A complex database setup, for instance, can be replaced with a lightweight in-memory solution, which is much simpler to initialize and control.

In iOS/macOS, UserDefaults, for instance, stores and reads from the file system. Using a real UserDefaults in tests can lead to side effects if there’s no proper cleanup after each and every test.

Core Data requires a complex setup, and reading and writing entries require a special context with access having to run on an specific thread. And as the UserDefaults example, it can leave leftovers between tests.

Implementation of alternative solutions:

  • For UserDefaults, a fake can be implemented using dictionaries, mapping keys to values.
  • For Core Data, the fake setup is even simpler: Core Data already offers the functionality to run in memory. In this case, the implementation requires setting the URL of the persistent store description to /dev/null before loading the persistent store.

Below, a possible implementation of a persistence service which uses core data underneath.

class PersistenceService {
    init(inMemory: Boolean = false) {
        let container = NSPersistentContainer(...)

        if inMemory {
            let storeDescription = container.persistentStoreDescriptions...

             storeDescription.url = URL(
                fileURLWithPath: "/dev/null"
            )
        }

        container.loadPersistentStores { description, error in
            // ...
        }
    }
}

In this case, a view model could take the persistence service which holds the entities in memory allowing to test the view model when data is added to the persistence service (and container).

func testShowMenuIfNotEmpty() {
    // Given
    let service = PersistenceService(inMemory: true)
    let viewModel = MenuViewModel(service: service)

    // When
	container.add("Burrito")

	// Then
    XCTAssertTrue(
        viewModel.shouldDisplayMenu
    )
}

The main difference between the two fakes mentioned above is that the first one, UserDefaults, requires a new type to be created and used in the tests. The latter, Core Data, doesn’t require a new type to be created, just a minor modification on the production code.

But either solutions achieve the same goal, injecting a type with real implementation that mimics the real object they replace so that it’s easier to test the subject under test.

In other words, fakes can improve test performance, reduce side effects in the testing environment, and allow determinism in test execution.

Bonus: Fakes are an interesting when working with SwiftUI previews as well.


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