Fixture vs Stub vs Mock in Swift Testing
Fixtures build test data, stubs control dependency return values, mocks verify interactions — here's the difference with Swift examples.
All three are test doubles — fake replacements for real dependencies in tests — but they serve distinct purposes.
Quick Mental Model
Each answers a different question:
- Fixture — What data do I start with?
- Stub — What should this dependency return?
- Mock — Was this dependency called correctly?
Fixture — Test Data
A fixture is predefined sample data for your models. It is not a replacement for a dependency — it is a convenient way to create realistic objects without filling in every field manually.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Foundation
extension User {
static func fixture(
id: UUID = UUID(),
name: String = "Test User",
email: String = "test@example.com",
isPremium: Bool = false
) -> User {
User(id: id, name: name, email: email, isPremium: isPremium)
}
}
// In your test:
let user = User.fixture(name: "Ali") // all other fields use safe defaults
Use when: You need a model object populated with sensible defaults and the data itself is not the focus of the test.
Stub — Hardcoded Responses
A stub is a fake implementation of a dependency that returns preset values. It replaces something like a network service or database so the test never performs real I/O. A stub cannot fail a test on its own — it only feeds your code the data it needs.
1
2
3
4
5
6
7
8
9
10
11
12
13
import Foundation
// Production protocol
protocol APIServiceProtocol {
func fetchUser(id: String) async throws -> User
}
// Stub: returns a canned response regardless of input
final class StubAPIService: APIServiceProtocol {
func fetchUser(id: String) async throws -> User {
return User.fixture()
}
}
Use when: Your code under test depends on something (network, database, disk) and you want to control what that dependency returns to test state or output.
Mock — Behavior Verifier
A mock is a stub that also records calls so you can assert on them. You are not only checking what your code returned — you are verifying your code behaved correctly: for example, did it call sendAnalyticsEvent() exactly once?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import Foundation
// Production protocol
protocol AnalyticsProtocol {
func trackEvent(_ name: String)
}
// Mock: tracks interactions
final class MockAnalyticsService: AnalyticsProtocol {
private(set) var didCallTrackEvent = false
private(set) var trackedEventName: String?
private(set) var callCount = 0
func trackEvent(_ name: String) {
didCallTrackEvent = true
trackedEventName = name
callCount += 1
}
}
// In your test:
import XCTest
final class LoginFeatureTests: XCTestCase {
func testLoginTracksEvent() async throws {
let mockAnalytics = MockAnalyticsService()
let sut = LoginViewModel(analytics: mockAnalytics)
try await sut.login(email: "ali@example.com", password: "secret")
XCTAssertTrue(mockAnalytics.didCallTrackEvent)
XCTAssertEqual(mockAnalytics.trackedEventName, "login")
XCTAssertEqual(mockAnalytics.callCount, 1)
}
}
Use when: You need to verify that your code interacted with a dependency in a specific way, not just that it returned the right value.
Side-by-Side
| Fixture | Stub | Mock | |
|---|---|---|---|
| What it is | Sample data builder | Fake dependency with preset return values | Fake dependency that records calls |
| Focus | Data setup | State verification | Behavior verification |
| Can fail a test? | No | No | Yes (via assertions on calls) |
| Asserted on? | No | No | Yes |
| Typical use | Creating model objects | Replacing network/DB | Verifying interactions (analytics, delegates, callbacks) |
| Swift example | User.fixture() | StubNetworkService | MockDelegate with var wasCalled = false |
The Rule of Thumb
In Swift unit testing with XCTest or Swift Testing, you will almost always use all three together:
- Fixture — build your test data
- Stub — control what dependencies return
- Mock — assert your code called the right things in the right way
☕ Support My Work
If you found this post helpful and want to support more content like this, you can buy me a coffee!
Your support helps me continue creating useful articles and tips for fellow developers. Thank you! 🙏