Dobby
Swift helpers for mocking and stubbing
Install / Use
/learn @trivago/DobbyREADME
Dobby
[!CAUTION] This repository has been archived as of October 2025.
If you wish to update or extend this project, please create a fork.
Dobby provides a few helpers for mocking and stubbing.
Matchers
Matchers can be matched with values, serving as the fundamental building block for mocking and stubbing. There are many functions that help creating matchers for (equatable) types, including optionals, tuples, arrays, and dictionaries with equatable elements:
matches { $0 == value } // matches value
any() // matches anything
not(0) // matches anything but 0
none() // matches Optional<T>.None (nil)
some(1) // matches Optional<T>.Some(1)
equals(1) // matches 1
equals((1, 2)) // matches (1, 2)
equals((1, 2, 3)) // matches (1, 2, 3)
equals((1, 2, 3, 4)) // matches (1, 2, 3, 4)
equals((1, 2, 3, 4, 5)) // matches (1, 2, 3, 4, 5)
equals([1, 2, 3]) // matches [1, 2, 3]
equals([1: 1, 2: 2, 3: 3]) // matches [1: 1, 2: 2, 3: 3]
Matchers may also be nested:
matches((matches { $0 == 0 }, any(), 2)) // matches (0, _, 2)
matches((not(equals(3)), some(any()))) // matches (not(3), _)
matches([any(), equals(4)]) // matches [_, 4]
matches(["key": matches { $0 == 5 }]) // matches ["key": 5]
Mocks
Mocks can be used to verify that all set up expectations have been fulfilled.
Strict mocks
By default, mocks are strict and the order of expectations matters, meaning all interactions must be expected and occur in the order they were expected:
let mock = Mock<[Int]>()
mock.expect(matches([any(), matches { $0 > 0 }])) // expects [_, n > 0]
mock.record([0, 1]) // succeeds
mock.verify() // succeeds
mock.record([1, 0]) // unexpected, fails (fast)
The order of expectations may also be ignored:
let mock = Mock<[Int]>(ordered: false)
mock.expect(matches([0, 1]))
mock.expect(matches([1, 0]))
mock.record([1, 0]) // succeeds
mock.record([0, 1]) // succeeds
mock.verify() // succeeds
mock.record([0, 0]) // unexpected, fails (fast)
Nice mocks
Nice mocks allow unexpected interactions while still respecting the order of expectations:
let mock = Mock<[Int?]>(strict: false)
mock.expect(matches([some(0)]))
mock.expect(matches([some(any())]))
mock.record([nil]) // unexpected, ignored
mock.record([1]) // out of order, ignored
mock.record([0]) // succeeds
mock.verify() // fails
mock.record([1]) // succeeds
mock.verify() // succeeds
Of course, nice mocks can ignore the order of expectations too:
let mock = Mock<[String: Int?]>(strict: false, ordered: false)
mock.expect(matches(["zero": some(0)]))
mock.expect(matches(["none": none()]))
mock.record(["some": 1]) // unexpected, ignored
mock.record(["none": nil]) // succeeds
mock.record(["zero": 0]) // succeeds
mock.verify() // succeeds
Negative expectations
In addition to normal expectations, nice mocks allow negative expectations to be set up:
let mock = Mock<Int>(nice: true)
mock.reject(0)
mock.record(0) // rejected, fails (fast)
Verification with delay
Verification may also be performed with a delay, allowing expectations to be fulfilled asynchronously:
let mock = Mock<Int>()
mock.expect(1)
let mainQueue: DispatchQueue = .main
mainQueue.asyncAfter(deadline: .now() + 1) {
mock.record(1) // succeeds
}
mock.verify(delay: 2) // succeeds
Stubs
Stubs, when invoked, return a value based on their set up behavior, or, if an interaction is unexpected, throw an error. Behavior is matched in order, i.e., the function or return value associated with the first matcher that matches an interaction is invoked/returned:
let stub = Stub<(Int, Int), Int>()
let behavior = stub.on(equals((2, 3)), return: 4)
stub.on(matches((any(), any()))) { x, y in x * y }
try! stub.invoke((2, 3)) // returns 4
try! stub.invoke((3, 3)) // returns 9
Behavior may also be disposed:
behavior.dispose()
try! stub.invoke((2, 3)) // returns 6
Example
The helpers provided for mocking and stubbing can be used with any testing approach, including protocol test implementations, test subclasses, etc. For example, imagine you want to verify interactions with the following class and change its behavior:
class MyClass {
func myMethod(fst: String, _ snd: String) -> String {
return fst + snd
}
}
Writing a test subclass for the given class is very simple:
class MyClassMock: MyClass {
let myMethodMock = Mock<(String, String)>()
let myMethodStub = Stub<(String, String), String>()
override func myMethod(fst: String, _ snd: String) -> String {
myMethodMock.record((fst, snd))
// Throw an exception if the stub doesn't define any behavior for the interaction.
return try! myMethodStub.invoke((fst, snd))
}
}
The test subclass allows you to verify that all your set up expectations are fulfilled and enables you to change its behavior on-the-fly:
let myClassMock = MyClassMock()
myClassMock.myMethodMock.expect(matches(("Hello", "World")))
myClassMock.myMethodStub.on(any()) { fst, snd in fst }
myClassMock.myMethod("Hello", "World") // returns "Hello"
myClassMock.myMethodMock.verify() // succeeds
If you ever find yourself wanting to use a mock or stub with several interactions of different types, consider using an equatable enum to define these interactions.
Documentation
Please check out the source and tests for further documentation.
About

Dobby was born at trivago 🏭
Related Skills
node-connect
349.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.5kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
