hamcrest/ochamcrest
OCHamcrest is an Objective-C module providing:
Contents
Predefined Matchers Object Number Text Logical Collection Decorator Syntactic Sugar Common Questions How Can I Assert on an Asynchronous Call? Can I Add Custom Matchers? What About Swift? How Do I Add OCHamcrest to My Project? Swift Package Manager CocoaPods Carthage Prebuilt Framework Build Your Own * About the Author<!-- endToc -->
My First OCHamcrest Test
We'll start by writing a very simple Xcode unit test, but instead of using XCTest's XCTAssertEqualObjects function, we'll use OCHamcrest's assertThat construct and a predefined matcher:
@import OCHamcrest;
@import XCTest;
@interface BiscuitTest : XCTestCase
@end
@implementation BiscuitTest
- (void)testEquals
{
Biscuit* theBiscuit = [[Biscuit alloc] initWithName:@"Ginger"];
Biscuit* myBiscuit = [[Biscuit alloc] initWithName:@"Ginger"];
assertThat(theBiscuit, equalTo(myBiscuit));
}
@endThe assertThat function is a stylized sentence for making a test assertion. In this example, the subject of the assertion is the object theBiscuit, which is the first method parameter. The second method parameter is a matcher for Biscuit objects, here a matcher that checks one object is equal to another using the -isEqual: method. The test passes since the Biscuit class defines an -isEqual: method.
OCHamcrest's functions are actually declared with an "HC_" package prefix (such as HC_assertThat and HC_equalTo) to avoid name clashes. To make test writing faster and test code more legible, optional short syntax is provided by default. For example, instead of writing HC_assertThat, simply write assertThat.
Predefined Matchers
OCHamcrest comes with a library of useful matchers:
Object
conformsTo - match object that conforms to protocol equalTo - match equal object hasDescription - match object's -description hasProperty - match return value of method with given name instanceOf - match object type isA - match object type precisely, no subclasses nilValue, notNilValue - match nil, or not nil sameInstance - match same object throwsException - match block that throws an exception HCArgumentCaptor - match anything, capturing all values
Number
closeTo - match number close to a given value greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - match numeric ordering isFalse - match zero isTrue - match non-zero
Text
containsSubstring - match part of a string endsWith - match the end of a string equalToIgnoringCase - match the complete string but ignore case equalToIgnoringWhitespace - match the complete string but ignore extra whitespace startsWith - match the beginning of a string stringContainsInOrder, stringContainsInOrderIn - match parts of a string, in relative order
Logical
allOf, allOfIn - "and" together all matchers anyOf, anyOfIn - "or" together all matchers anything - match anything (useful in composite matchers when you don't care about a particular value) isNot - negate the matcher
Collection
contains, containsIn - exactly match the entire collection containsInAnyOrder, containsInAnyOrderIn - match the entire collection, but in any order containsInRelativeOrder, containsInRelativeOrderIn - match collection containing items in relative order everyItem - match if every item in a collection satisfies a given matcher hasCount - match number of elements against another matcher hasCountOf - match collection with given number of elements hasEntries - match dictionary with key-value pairs in a dictionary hasEntriesIn - match dictionary with key-value pairs in a list hasEntry - match dictionary containing a key-value pair hasItem - match if given item appears in the collection hasItems, hasItemsIn - match if all given items appear in the collection, in any order hasKey - match dictionary with a key hasValue - match dictionary with a value isEmpty - match empty collection isIn - match when object is in given collection onlyContains, onlyContainsIn - match if collection's items appear in given list
Decorator
describedAs - give the matcher a custom failure description is - decorator to improve readability - see "Syntactic sugar" below
The arguments for many of these matchers accept not just a matching value, but another matcher, so matchers can be composed for greater flexibility. For example, only_contains(endsWith(@".")) will match any collection where every item is a string ending with period.
Syntactic Sugar
OCHamcrest strives to make your tests as readable as possible. For example, the is matcher is a wrapper that doesn't add any extra behavior to the underlying matcher. The following assertions are all equivalent:
assertThat(theBiscuit, equalTo(myBiscuit));
assertThat(theBiscuit, is(equalTo(myBiscuit)));
assertThat(theBiscuit, is(myBiscuit));The last form is allowed since is wraps non-matcher arguments with equalTo. Other matchers that take matchers as arguments provide similar shortcuts, wrapping non-matcher arguments in equalTo.
Common Questions
How Can I Assert on an Asynchronous Call?
assertWithTimeout will keep evaluating an expression until the matcher is satisfied or a timeout is reached. For example,
assertWithTimeout(5, thatEventually(self.someString), is(@"expected"));This repeatedly checks for this string to evaluate to "expected" before timing out after 5 seconds. thatEventually is a convenience macro to create a block.
Can I Add Custom Matchers?
OCHamcrest comes bundled with lots of useful matchers, but you'll probably find that you need to create your own from time to time to fit your testing needs. See the "Writing Custom Matchers" guide for more information.
What About Swift?
How Do I Add OCHamcrest to My Project?
The Examples folder shows projects ready to use OCHamcrest via Swift Package Manager, CocoaPods, or through the prebuilt framework.
Swift Package Manager
Include an OCHamcrest package in your Package.swift manifest's array of dependencies:
<a id='snippet-swiftpm-declare-dependencies'></a>
dependencies: [
.package(
url: "https://github.com/hamcrest/OCHamcrest",
.upToNextMajor(from: "9.1.0")
),
],<sup><a href='/Examples/MacExample-SwiftPackageManager/Package.swift#L13-L20' title='Snippet source file'>snippet source</a> | <a href='#snippet-swiftpm-declare-dependencies' title='Start of snippet'>anchor</a></sup>
Then add OCHamcrest to the dependencies of your .testTarget:
<a id='snippet-swiftpm-use-dependencies'></a>
.testTarget(
name: "ExampleTests",
dependencies: [
"Example",
"OCHamcrest",
]
),<sup><a href='/Examples/MacExample-SwiftPackageManager/Package.swift#L27-L35' title='Snippet source file'>snippet source</a> | <a href='#snippet-swiftpm-use-dependencies' title='Start of snippet'>anchor</a></sup>
CocoaPods
If you want to add OCHamcrest using Cocoapods then add the following dependency to your Podfile. Most people will want OCHamcrest in their test targets, and not include any pods from their main targets:
target 'MyTests' do
inherit! :search_paths
use_frameworks!
pod 'OCHamcrest', '~> 9.1'
endCarthage
Add the following to your Cartfile:
github "hamcrest/OCHamcrest" ~> 9.1
Then drag the the built framework from the appropriate Carthage/Build directory into your project, but with "Copy items into destination group's folder" disabled.
Prebuilt Framework
A prebuilt binary is available on GitHub. The binary is packaged as OCHamcrest.xcframework, containing these architectures: macOS Mac Catalyst iOS device iOS simulator tvOS device tvOS simulator visionOS device visionOS simulator watchOS device watchOS simulator
Drag the XCFramework into your project.
Build Your Own
If you want to build OCHamcrest yourself, clone the repo, then
$ cd Resources
$ ./MakeDistribution.shPackage Metadata
Repository: hamcrest/ochamcrest
Default branch: main
README: README.md