Skip to content

Quick & Nimble

TestingSwift

What it is

Quick is a behaviour-driven testing framework for Swift and Objective-C; Nimble is its matcher library, providing expressive assertions and async expectations.

Specs nest describe, context and it blocks with beforeEach setup. Nimble matchers read as sentences and handle asynchronous expectations.

Installation

.package(url: "https://github.com/Quick/Quick.git", from: "7.6.0")

Getting started

The smallest useful thing you can do with it, and what each part means.

Nested specs
import Quick
import Nimble

final class BookServiceSpec: QuickSpec {
    override class func spec() {
        var service: BookService!
        var repository: MockRepository!

        beforeEach {
            repository = MockRepository()
            service = BookService(repository: repository)
        }

        describe("fetching a book") {
            context("when it exists") {
                beforeEach { repository.stubbed = Book(id: 1, title: "Dune") }

                it("returns the title") {
                    expect(service.title(for: 1)).to(equal("Dune"))
                }
            }

            context("when it is missing") {
                beforeEach { repository.stubbed = nil }

                it("throws notFound") {
                    expect { try service.require(1) }
                        .to(throwError(BookError.notFound))
                }
            }
        }
    }
}
The nesting produces test names that read as sentences, so a CI failure describes the behaviour that broke rather than naming a method.

Advanced usage

Where the library earns its place over a simpler alternative.

Async expectations and custom matchers
// Polls until the condition holds or the timeout expires.
await expect(viewModel.books).toEventually(haveCount(3), timeout: .seconds(2))
await expect(viewModel.isLoading).toEventually(beFalse())

// Async/await support.
await expect { try await service.fetch() }.to(haveCount(3))

// A reusable custom matcher.
func bePublished(inYear year: Int) -> Matcher<Book> {
    Matcher { actual in
        guard let book = try actual.evaluate() else {
            return MatcherResult(status: .fail, message: .expectedTo("be non-nil"))
        }
        return MatcherResult(
            bool: book.year == year,
            message: .expectedTo("be published in \(year), got \(book.year)")
        )
    }
}
toEventually polls rather than sleeping a fixed interval, which removes the arbitrary waits that make asynchronous tests flaky.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Tests pass alone but fail together
State declared outside beforeEach persists between examples. Reset everything in beforeEach.
toEventually never succeeds
The value updates on a queue the test does not pump, or the timeout is too short. Check which thread the update happens on.

Best practices

  • Use beforeEach for setup so each example starts clean; shared mutable state across examples causes order-dependent failures.
  • Prefer toEventually over fixed sleeps for async assertions.
  • Consider Nimble alone with XCTest if the team does not want the BDD structure.
  • Evaluate Swift Testing for new projects — it now covers much of this natively.

Background

Why it exists, and what it was reacting to.

Modelled on RSpec, Quick brought nested describe and context blocks to Swift. Nimble is usable on its own with XCTest, and many teams adopt only the matchers.