Test Double in Swift - Stub
We replace a real object with a test-specific object that feeds the desired indirect inputs into the system under test.
- How can we verify logic independently when it depends on indirect inputs from other software components?
When we need defined return values from a method. In a test, it is often useful to have fixed hard-coded return value for a method that the SUT calls. The test then asserts that the SUT reacts in an expected way to the defined return value.
Stubs may also record information about calls, such as an email gateway stub that remembers the messages it ‘sent’, or maybe only how many messages it ‘sent’.
How it works
- Define a test-specific implementation of an interface on which the SUT depends.
- Install the Stub so that the SUT uses it instead of the real implementation.
- Called by the SUT during test execution, the Stub returns the previously defined values. The test can then verify the expected outcome.
Note: Test-driven development often causes us to create Temporary Test Stubs as we write code from the outside inwards; these shells evolve into the real classes as we add code to them. We really should have at least one test that verifies it works without a Test Stub.
Motivating Example
protocol Service {
func hello(_ name: String) -> String
}
class Greet {
let service: Service
init(_ service: Service) { self.service = service }
func greeting(_ name: String) -> String { return self.service.hello(name) }
}
class StubService: Service {
var greeting: String?
init(with: greeting: String) { self.greeting = greeting }
// Service interface
func hello(_ name: String) -> String { return greeting ?? "" }
}
class StubTests: XCTestCase {
func test_greeting_withService_shouldReturnFoo() {
let sut = Greet(StubService(with: "foo"))
let resut = sut.greeting("dummy")
XCTAssertEqual(resut, "foo")
}
}