SkillAgentSearch skills...

Dobby

Swift helpers for mocking and stubbing

Install / Use

/learn @trivago/Dobby
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

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

View on GitHub
GitHub Stars165
CategoryDevelopment
Updated6mo ago
Forks14

Languages

Swift

Security Score

87/100

Audited on Oct 2, 2025

No findings