• Stars
    star
    260
  • Rank 157,189 (Top 4 %)
  • Language
    Swift
  • License
    Other
  • Created about 10 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Hamcrest test assertions for Swift

Swift Hamcrest

Build Status](https://github.com/nschum/SwiftHamcrest/actions/workflows/build.yml/badge.svg) Swift 5.7 OS X β‰₯ 11.0 iOS β‰₯ 11.0 Carthage compatible](https://github.com/Carthage/Carthage) CocoaPods compatible

Hamcrest gives you advanced matchers with better error messages for your Swift unit tests.

Hamcrest was originally written in Java and is available for many languages.

Tutorial

This tutorial is also available as a Playground in the Hamcrest workspace.

Normally, you use these matchers in unit tests, where a mismatch will cause the test to fail, but they also work in Playgrounds, where a mismatch will simply print the error message.

In either case, the Hamcrest module needs to be imported.

import Hamcrest

Operator Matchers

The following are very simply matchers. The matched expressions look like regular boolean expressions, but provide readable mismatch messages instead of a generic error.

let x = 1 + 1

// The comments show the human-readable error messages created by the assertions.

assertThat(x == 2) // βœ“
assertThat(x == 3) // GOT: 2, EXPECTED: equal to 3

assertThat(x > 1) // βœ“
assertThat(x > 2) // GOT: 2, EXPECTED: greater than 2

assertThat(x >= 2) // βœ“
assertThat(x >= 3) // GOT: 2, EXPECTED: greater than or equal to 3

assertThat(x < 3) // βœ“
assertThat(x < 2) // GOT: 2, EXPECTED: less than 2

assertThat(x <= 2) // βœ“
assertThat(x <= 1) // GOT: 2, EXPECTED: less than or equal to 1

class Test {}
let o = Test()
assertThat(o === o) // βœ“
assertThat(o === Test())
// GOT: __lldb_expr_8.Test (0x7f9572b020d0),
// EXPECTED: same instance as 0x7f9570c702a0

Textual Matchers

All these matchers are also available as functions.

assertThat(x, equalTo(2)) // βœ“
assertThat(x, equalTo(3)) // GOT: 2, EXPECTED: equal to 3

assertThat(x, greaterThan(1)) // βœ“
assertThat(x, greaterThan(2)) // GOT: 2, EXPECTED: greater than 2

assertThat(x, greaterThanOrEqualTo(2)) // βœ“
assertThat(x, greaterThanOrEqualTo(3))
// GOT: 2, EXPECTED: greater than or equal to 3

assertThat(x, lessThan(3)) // βœ“
assertThat(x, lessThan(2)) // GOT: 2, EXPECTED: less than 2

assertThat(x, lessThanOrEqualTo(2)) // βœ“
assertThat(x, lessThanOrEqualTo(1))
// GOT: 2, EXPECTED: less than or equal to 1

assertThat(x, inInterval(1...2)) // βœ“
assertThat(x, inInterval(1..<2)) // GOT: 2, EXPECTED: in interval 1..<2

assertThat(o, sameInstance(o)) // βœ“
assertThat(o, sameInstance(Test()))
// GOT: __lldb_expr_53.Test, EXPECTED: same instance as __lldb_expr_53.Test

Here are some more straightforward matchers:

assertThat("foobarbaz", containsString("bar")) // βœ“
assertThat("foobarbaz", containsString("bla"))
// GOT: "foobarbaz", EXPECTED: contains "bla"

assertThat("foobarbaz", containsStringsInOrder("f", "b", "b")) // βœ“
assertThat("foobarbaz", containsStringsInOrder("foo", "baz", "bar"))
// GOT: "foobarbaz", EXPECTED: contains in order ["foo", "baz", "bar"]

assertThat("foobarbaz", hasPrefix("foo")) // βœ“
assertThat("foobarbaz", hasPrefix("oo"))
// GOT: "foobarbaz", EXPECTED: has prefix "oo"

assertThat("foobarbaz", hasSuffix("baz")) // βœ“
assertThat("foobarbaz", hasSuffix("ba"))
// GOT: "foobarbaz", EXPECTED: has suffix "ba"

assertThat("ac", matchesPattern("\\b(a|b)(c|d)\\b")) // βœ“
assertThat("BD", matchesPattern("\\b(a|b)(c|d)\\b", options: .caseInsensitive)) // βœ“
assertThat("aC", matchesPattern("\\b(a|b)(c|d)\\b"))
// "GOT: "aC", EXPECTED: matches \b(a|b)(c|d)\b"

assertThat(10.0, closeTo(10.0, 0.01)) // βœ“
assertThat(10.0000001, closeTo(10, 0.01)) // βœ“
assertThat(10.1, closeTo(10, 0.01))
// GOT: 10.1 (difference of 0.0999999999999996), EXPECTED: within 0.01 of 10.0

import Foundation
assertThat(CGPoint(x: 5, y: 10), hasProperty("x", closeTo(5.0, 0.00001))) // βœ“
assertThat(CGPoint(x: 5, y: 10), hasProperty("y", closeTo(0.0, 0.00001)))
// GOT: (5.0,10.0) (property value 10.0 (difference of 10.0)),
// EXPECTED: has property "y" with value within 1e-05 of 0.0

Combining Matchers

The real power of Hamcrest comes combining multiple matchers into a single assertion statement.

assertThat(x, not(equalTo(3))) // βœ“
assertThat(x, not(equalTo(2))) // GOT: 2, EXPECTED: not equal to 2

assertThat(x, allOf(greaterThan(1), lessThan(3))) // βœ“
assertThat(x, allOf(greaterThan(2), lessThan(3)))
// GOT: 2 (mismatch: greater than 2),
// EXPECTED: all of [greater than 2, greater than 3]

assertThat(x, greaterThan(1) && lessThan(3)) // βœ“
assertThat(x, greaterThan(2) && lessThan(3))
// GOT: 2 (mismatch: greater than 2),
// EXPECTED: all of [greater than 2, greater than 3]

assertThat(x, anyOf(greaterThan(2), lessThan(3))) // βœ“
assertThat(x, anyOf(greaterThan(2), lessThan(2)))
// GOT: 2, EXPECTED: any of [greater than 2, greater than 2]

assertThat(x, greaterThan(2) || lessThan(3)) // βœ“
assertThat(x, greaterThan(2) || lessThan(2))
// GOT: 2, EXPECTED: any of [greater than 2, greater than 2]

Collections

Combining matchers is particularly useful for matching sequences and dictionaries.

let array = ["foo", "bar"]

assertThat(array, hasCount(2)) // βœ“
assertThat(array, hasCount(greaterThan(2)))
// GOT: [foo, bar] (count 2), EXPECTED: has count greater than 2

assertThat(array, everyItem(equalTo("foo")))
// GOT: [foo, bar] (mismatch: bar),
// EXPECTED: a sequence where every item equal to foo

assertThat(array, contains("foo", "bar")) // βœ“
assertThat(array, contains(equalTo("foo"), equalTo("bar"))) // βœ“
assertThat(array, contains(equalTo("foo")))
// GOT: [foo, bar] (unmatched item "bar"),
// EXPECTED: a sequence containing equal to foo
assertThat(array, contains(equalTo("foo"), equalTo("baz")))
// "GOT: [foo, bar] (mismatch: GOT: "bar", EXPECTED: equal to baz),
// EXPECTED: a sequence containing [equal to foo, equal to baz]"
assertThat(array, contains(equalTo("foo"), equalTo("bar"), equalTo("baz")))
// GOT: [foo, bar] (missing item equal to baz),
// EXPECTED: a sequence containing [equal to foo, equal to bar, equal to baz]

assertThat(array, containsInAnyOrder("bar", "foo")) // βœ“
assertThat(array, containsInAnyOrder(equalTo("bar"), equalTo("foo"))) // βœ“

assertThat(array, hasItem(equalTo("foo"))) // βœ“
assertThat(array, hasItem(equalTo("baz")))
// GOT: [foo, bar], EXPECTED: a sequence containing equal to baz

assertThat(array, hasItem("foo", atIndex: 0))) // βœ“
assertThat(array, hasItem("foo", atIndex: 1))) // GOT: ["foo", "bar"], EXPECTED: a sequence containing "foo" at index 1"

assertThat(array, hasItem(equalTo("foo"), atIndex: 0))) // βœ“
assertThat(array, hasItem(equalTo("foo"), atIndex: 1))) // GOT: ["foo", "bar"], EXPECTED: a sequence containing "foo" at index 1"


assertThat(array, hasItems("foo", "bar")) // βœ“
assertThat(array, hasItems(equalTo("foo"), equalTo("baz")))
// GOT: [foo, bar] (missing item equal to baz),
// EXPECTED: a sequence containing all of [equal to foo, equal to baz]
let dictionary = ["foo": 5, "bar": 10]

assertThat(dictionary, hasEntry("foo", 5)) // βœ“
assertThat(dictionary, hasEntry(equalTo("foo"), equalTo(5))) // βœ“
assertThat(dictionary, hasEntry(equalTo("foo"), equalTo(10)))
// GOT: [bar: 10, foo: 5],
// EXPECTED: a dictionary containing [equal to foo -> equal to 10]

assertThat(dictionary, hasKey("foo")) // βœ“
assertThat(dictionary, hasKey(equalTo("baz")))
// GOT: [bar: 10, foo: 5],
// EXPECTED: a dictionary containing [equal to baz -> anything]

assertThat(dictionary, hasValue(10)) // βœ“
assertThat(dictionary, hasValue(equalTo(15)))
// GOT: [bar: 10, foo: 5],
// EXPECTED: a dictionary containing [anything -> equal to 15]

Optional types

Matchers don't expect optional types to match Swift's favoring of non-nilable types. presentAnd can be explicitly apply a matcher to an optional type.

var optional: Int = 1 + 1

assertThat(optional, present()) // βœ“
assertThat(optional, nilValue()) // GOT: Optional(2), EXPECTED: nil

assertThat(optional, presentAnd(equalTo(2))) // βœ“
assertThat(optional, presentAnd(equalTo(1)))
// GOT: Optional(2), EXPECTED: present and equal to 1

Types and Casts

The following matchers can be used to assert types. References of type Any need to be cast before typed matchers can be used. instanceOf(and:) can be used to combine type verification and casting.

class TestChild: Test {}
assertThat(o, instanceOf(Test.self)) // βœ“
assertThat(o, instanceOf(TestChild.self))
// GOT: __lldb_expr_60.Test, EXPECTED: instance of expected type

let any: Any = 10
assertThat(any, instanceOf(Int.self, and: equalTo(10))) // βœ“
assertThat(any, instanceOf(Double.self, and: equalTo(10.0)))
// GOT: 10 (mismatched type), EXPECTED: instance of and equal to 10.0
assertThat(any, instanceOf(Int.self, and: equalTo(5)))
// GOT: 10, EXPECTED: instance of and equal to 5

Custom Matchers

There are two ways of creating custom matchers. The first way is to create a function that simply returns a combination of existing matchers.

func isOnAxis<Point>() -> Matcher<Point> {
    return anyOf(hasProperty("x", closeTo(0.0, 0.00001)),
                 hasProperty("y", closeTo(0.0, 0.00001)))
}

assertThat(CGPoint(x: 0, y: 10), isOnAxis()) // βœ“
assertThat(CGPoint(x: 5, y: 10), isOnAxis())
// GOT: (5.0,10.0),
// EXPECTED: any of [has property "x" with value within 1e-05
// of 0.0, has property "y" with value within 1e-05 of 0.0]

You can use the special matcher describedAs to customize the description.

func isOnAxis2<Point>() -> Matcher<Point> {
    return describedAs("a point on an axis",
        anyOf(hasProperty("x", closeTo(0.0, 0.00001)),
              hasProperty("y", closeTo(0.0, 0.00001))))
}

assertThat(CGPoint(x: 0, y: 10), isOnAxis2()) // βœ“
assertThat(CGPoint(x: 5, y: 10), isOnAxis2())
// GOT: (5.0,10.0), EXPECTED: a point on an axis

The second way is to create a matcher from scratch. SwiftHamcrest particularly focuses on making this kind of custom matchers easy to write. In many Hamcrest implementations, you usually create a class for this. In SwiftHamcrest, you just create an instance of Matcher with a custom closure that takes a value and returns a Bool.

func isEven() -> Matcher<Int> {
    return Matcher("even") {$0 % 2 == 0}
}

assertThat(x, isEven()) // βœ“
assertThat(3, isEven()) // GOT: 3, EXPECTED: even

While a Bool is convenient (and sufficient in most cases), there are occasions where you want more information about the mismatch. Instead of a Bool you can also have the closure return a MatchResult enum. This is especially useful if the mismatch isn't obvious.

func isDivisibleByThree() -> Matcher<Int> {
    return Matcher("divisible by three") {
        (value) -> MatchResult in
        if value % 3 == 0 {
            return .Match
        } else {
            return .Mismatch("remainder: \(value % 3)")
        }
    }
}

assertThat(342783, isDivisibleByThree()) // βœ“
assertThat(489359, isDivisibleByThree())
// GOT: 489359 (remainder: 2), EXPECTED: divisible by three

Errors

If the function you're testing can throw errors, Hamcrest will report those errors.

private enum SampleError: Error {
    case Error1
    case Error2
}

private func throwingFunc() throws -> Int {
    throw SampleError.Error1
}

assertThat(try throwingFunc(), equalTo(1)) // ERROR: SampleError.Error1

If you don't want to test the result of a function that can throw errors, or if this function does not return any error, use assertNotThrows.

private func notThrowingFunc() throws {
}

assertNotThrows(try notThrowingFunc()) // βœ“
assertNotThrows(_ = try throwingFunc()) // ERROR: UNEXPECTED ERROR

If you want to verify an error is being thrown, use assertThrows.

assertThrows(try notThrowingFunc()) // EXPECTED ERROR
assertThrows(try notThrowingFunc(), SampleError.Error2)
// EXPECTED ERROR: SampleError.Error2

assertThrows(try throwingFunc(), SampleError.Error2)
// GOT ERROR: SampleError.Error1, EXPECTED ERROR: SampleError.Error2

Message

If the matcher does not give a meaningful message, you can add a custom message that it displayed when the matcher does not match.

assertThat(true, equalTo(false), message: "Custom error message")

// failed: Custom error message – GOT: true, EXPECTED: equal to false

Integration

Swift Package Manager

Select the 'Add Package Dependency' option in your Xcode project and copy this repository's URL into the 'Choose Package Repository' window.

CocoaPods

Integrate SwiftHamcrest using a Podfile similar to this:

use_frameworks!

target 'HamcrestDemoTests' do
  inherit! :search_paths
  pod 'SwiftHamcrest', '~> 2.2.4'
end

Carthage

Add the following to your Cartfile:

github "nschum/SwiftHamcrest"

More Repositories

1

FontAwesomeIconFactory

A factory for turning Font Awesome icons into images for user interface controls. Works for iOS and OS X.
Objective-C
365
star
2

Gitifier

Git commit notifier for MacOSX
Objective-C
314
star
3

highlight-symbol.el

Emacs: automatic and manual symbol highlighting
Emacs Lisp
276
star
4

window-numbering.el

Emacs: Numbered window shortcuts
Emacs Lisp
162
star
5

SwiftCGRectExtensions

A collection of CGRect, CGPoint and CGSize convenience functions for Swift
Swift
120
star
6

full-ack

An Emacs front-end for ack
Emacs Lisp
66
star
7

AutoLayoutMacros

Macros for making Auto Layout more readable (and writable) without using strings.
Objective-C
54
star
8

fringe-helper.el

Emacs: helper functions for fringe bitmaps
Emacs Lisp
47
star
9

homebridge-twinkly

JavaScript
37
star
10

guess-style

Emacs: automatic setting of code style variables
Emacs Lisp
32
star
11

auto-dictionary-mode

Emacs: automatic dictionary switcher for flyspell
Emacs Lisp
27
star
12

rotate-text.el

Emacs: cycle through words, symbols and patterns
Emacs Lisp
19
star
13

kill-ring-search.el

Emacs: incremental search for the kill ring
Emacs Lisp
10
star
14

idle-require.el

Emacs: load elisp libraries while Emacs is idle
Emacs Lisp
9
star
15

macro-math.el

Emacs: in-buffer mathematical operations
Emacs Lisp
7
star
16

talks

talk slides
Swift
6
star
17

doc-mode

Emacs: convenient editing of in-code documentation
Emacs Lisp
6
star
18

echo-pick.el

Emacs: filter for echo area status messages
Emacs Lisp
3
star
19

tempo-snippets.el

visual insertion of templates for Emacs
Emacs Lisp
2
star
20

async-eval.el

Emacs: execute Emacs lisp in a separate process
Emacs Lisp
2
star
21

recent-addresses.el

Emacs: store and recall recently used email addresses
Emacs Lisp
1
star
22

elk-test

Emacs Lisp testing framework
Emacs Lisp
1
star
23

compile-bookmarks.el

Emacs: bookmarks for compilation commands
Emacs Lisp
1
star