SwiftCheck Documentation

repository·master·Indexed 23 days ago

https://github.com/typelift/swiftcheck

A property-based testing library for Swift inspired by Haskell's QuickCheck. SwiftCheck allows developers to define program properties using the forAll quantifier and the Arbitrary protocol, automatically testing them against randomly generated data and shrinking failing test cases to minimal counterexamples. It includes generator combinators such as suchThat, fromElements, choose, and frequency for creating custom test data.

Tokens
1.4K
Snippets
4
Records
5
Agent score
31%

What's inside SwiftCheck

  1. How shrinking works in SwiftCheck

    master
    When a test fails during fuzz testing, SwiftCheck doesn't just stop. It performs shrinking: it attempts to whittle down the failing input data to the smallest possible counterexample that still triggers the failure. This makes debugging much easier by providing a minimal reproducible case.
  2. Define program properties with `forAll`

    master

    SwiftCheck uses the forAll quantifier to define properties. A property is an invariant that must hold true for all generated data of a specific type. To use forAll, the input types must conform to the Arbitrary protocol.

    Basic usage:

    property("Description") <- forAll { (i : Int) in
        return i == i
    }

    Advanced Property Operators

    • ^&&^ (Conjunction): Combines multiple properties. The resulting property only holds if all sub-properties hold.
    • <?> (Labeling): Labels a sub-part of a property for clearer output in failure reports.
    • ==> (Implication): Defines a precondition. If the precondition returns false, the test case is discarded and not evaluated against the property.

    Example of a complex property:

    property("The reverse of the reverse of an array is that array") <- forAll { (xs : [Int]) in
        return
            (xs.reversed().reversed() == xs) <?> "Left identity"
            ^&&^
            (xs == xs.reversed().reversed()) <?> "Right identity"
    }
    property("Integer Equality is Reflexive") <- forAll { (i : Int) in
        return i == i
    }
  3. Install SwiftCheck

    master

    You can integrate SwiftCheck into your project using Swift Package Manager, Carthage, CocoaPods, or as a Framework subproject.

    Swift Package Manager

    Add the following to your Package.swift dependencies:

    .package(url: "https://github.com/typelift/SwiftCheck.git", from: "0.8.1")

    Carthage

    1. Add SwiftCheck to your Cartfile.
    2. Run carthage update.
    3. Drag the relevant copy of SwiftCheck into your project.
    4. Expand the Link Binary With Libraries phase and add SwiftCheck.
    5. Add a Copy Files build phase, set the directory to Frameworks, and add SwiftCheck.

    CocoaPods

    Add the pod to your Podfile and run:

    $ pod install

    Framework (Subproject)

    1. Drag SwiftCheck.xcodeproj into your project tree as a subproject.
    2. Under Target Dependencies, add SwiftCheck.
    3. Expand Link Binary With Libraries and add SwiftCheck.
    4. Add a Copy Files build phase, set the directory to Frameworks, and add SwiftCheck.
  4. Use Generator combinators to create custom data

    master

    SwiftCheck provides several combinators to build specialized generators from existing ones:

    • suchThat: Filters a generator based on a predicate (e.g., Int.arbitrary.suchThat { $0 % 2 == 0 }).
    • fromElements(of:): Generates values from a specific collection.
    • choose((min, max)): Generates a random value within a range.
    • frequency([(weight, generator)]): Generates values based on weighted probabilities.
    • pure(value): Creates a generator that always returns the same value.

    Example:

    let onlyEven = Int.arbitrary.suchThat { $0 % 2 == 0 }
    let vowels = Gen.fromElements(of: [ "A", "E", "I", "O", "U" ])
    let randomHexValue = Gen<UInt>.choose((0, 15))
    
    let weightedOptionals = Gen<Int?>.frequency([
        (1, Gen<Int?>.pure(nil)),
        (3, Int.arbitrary.map(Optional.some))
    ])
    let onlyEven = Int.arbitrary.suchThat { $0 % 2 == 0 }
    
    let vowels = Gen.fromElements(of: [ "A", "E", "I", "O", "U" ])
    
    let randomHexValue = Gen<UInt>.choose((0, 15))
    
    let uppers = Gen<Character>.fromElements(in: "A"..."Z")
    let lowers = Gen<Character>.fromElements(in: "a"..."z")
    let numbers = Gen<Character>.fromElements(in: "0"..."9")
     
    /// This generator will generate `.none` 1/4 of the time and an arbitrary
    /// `.some` 3/4 of the time
    let weightedOptionals = Gen<Int?>.frequency([
        (1, Gen<Int?>.pure(nil)),
        (3, Int.arbitrary.map(Optional.some))
    ])
  5. Make custom types testable with `Arbitrary`

    master

    To use custom types with forAll, they must conform to the Arbitrary protocol. This requires implementing a static arbitrary property that returns a Gen<T> (Generator).

    Implementing Arbitrary via Gen.zip

    For types with multiple properties, you can zip existing generators together:

    extension ArbitraryFoo : Arbitrary {
        public static var arbitrary : Gen<ArbitraryFoo> {
            return Gen<(Int, Int)>.zip(Int.arbitrary, Int.arbitrary).map(ArbitraryFoo.init)
        }
    }

    Implementing Arbitrary via Gen.compose

    For more complex construction (e.g., using setters or procedural logic), use Gen.compose. This provides a context c that allows you to call c.generate() for various types:

    public static var arbitrary : Gen<MyClass> {
        return Gen<MyClass>.compose { c in
            return MyClass(
                a: c.generate(),
                b: c.generate(Bool.suchThat { $0 == false }),
                c: c.generate()
            )
        }
    }