OCHamcrest: composable matchers for Objective-C tests
Hamcrest for Objective-C: Powerful, combinable, extensible matchers for verification
At a glance
- What is it?
- OCHamcrest is the Objective-C port of the Hamcrest matcher library, giving XCTest an assertThat style assertion where the rule is an object that can be combined with others. It ships through four distribution channels, and its last push was on 2026-06-18 with a release history whose newest tag is from February 2025.
- Who is it for?
- Adopt OCHamcrest when you write Objective-C tests where plain XCTAssertEqualObjects cannot express the rule you care about, and when you are willing to learn the matcher vocabulary. Do not adopt it for a Swift-only codebase, since the documentation addresses Swift separately, and do not adopt it because a failure message needs to be prettier, since the readability gain comes from the matcher vocabulary rather than the output format.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 126 days ago.
- What is it written in?
- Mainly Objective-C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An assertion where the rule is a first class object
OCHamcrest is an Objective-C module that provides two things: a library of matcher objects for declaring rules to check whether a given object matches those rules, and a framework for writing your own matchers. The point of the design is that matchers can be combined to create flexible expressions of intent in tests, and the documentation notes they can be used for other purposes too, such as user input validation.
The starting example is a plain XCTest test rewritten. Instead of XCTest's XCTAssertEqualObjects, the assertion goes through 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 described as a stylized sentence for making a test assertion. The subject of the assertion is the first parameter, the object, and the second is a matcher, here one that checks one object is equal to another using the -isEqual: method, which is why the test passes for a Biscuit class that defines -isEqual:.
That structure is the whole idea. The rule lives in an object rather than inside a comparison expression, so rules can be named, reused, negated and combined.
The HC_ prefix and the short syntax that hides it
One implementation detail is worth knowing before you write a line of test code. The functions are actually declared with an HC_ package prefix, such as HC_assertThat and HC_equalTo, to avoid name clashes with other libraries that might define the same names. To make test writing faster and test code more legible, a short syntax is provided by default, so you write assertThat rather than HC_assertThat.
The practical consequence is that the readable form is a convenience layer, not the real API. If you are reading older code, or code from a project that turned the short syntax off, you will encounter the prefixed names. Conversely, if you are writing a library that exposes matchers to other teams, the prefixed symbols are the ones that will not collide with a future matcher's name.
The prefix also explains a design choice about matcher names. Every predefined matcher carries a descriptive verb phrase, equalTo, hasDescription, containsSubstring, hasCountOf, and the composition operators allOf, anyOf and isNot. Read as a sentence, assertThat(theBiscuit, equalTo(myBiscuit)) says what is being checked. The library is not trying to be terse here, it is trying to make a failing expectation legible at the call site.
What the matcher families cover
The predefined library is grouped into families, and reading them tells you what kind of assertion is easy to express.
The object family covers conformsTo, equalTo, hasDescription, hasProperty, instanceOf, isA for a precise type with no subclasses, nilValue and notNilValue, sameInstance, throwsException for a block that throws, and HCArgumentCaptor, which matches anything while capturing all the values passed to it. The number family has closeTo, the ordering comparators greaterThan, greaterThanOrEqualTo, lessThan and lessThanOrEqualTo, and isTrue and isFalse, which match non-zero and zero.
Text adds containsSubstring, endsWith, startsWith, equalToIgnoringCase, equalToIgnoringWhitespace, and stringContainsInOrder with its in-collection variant. Logical adds allOf and anyOf with their in-collection variants, anything, and isNot.
The collection family is the largest: contains, containsInAnyOrder, containsInRelativeOrder and their in-variants, everyItem, hasCount, hasCountOf, hasEntry, hasEntries, hasEntriesIn, hasItem, hasItems, hasItemsIn, hasKey, hasValue, isEmpty, isIn and onlyContains with onlyContainsIn. The decorator family adds describedAs for a custom failure description and is, which is described as a decorator to improve readability.
The naming convention is consistent and worth internalising: the plain form takes a collection argument, and the In form takes a list, so containsIn and hasItemsIn are the ones you reach for when the expected items are given as an array literal.
Matchers take matchers, which is what makes them composable
The mechanism that turns a list of matchers into a library is that the arguments of many matchers accept not just a matching value but another matcher.
The documented example is only one line long. only_contains(endsWith(@".")) matches any collection where every item is a string ending with a period. The inner matcher checks the shape of an item, the outer matcher applies it to every item, and neither knew about the other when it was written.
The decorator `is` exists for readability rather than behaviour, since it adds no extra behavior to the underlying matcher. These three assertions are equivalent:
assertThat(theBiscuit, equalTo(myBiscuit));
assertThat(theBiscuit, is(equalTo(myBiscuit)));
assertThat(theBiscuit, is(myBiscuit));The last form is allowed because is wraps non-matcher arguments with equalTo, so is(myBiscuit) becomes is(equalTo(myBiscuit)). That single rule is what lets you write is(equalTo(x)) when you mean it and is(x) when you do not, and it is a good example of the library's overall approach: the surface stays small by having the wrapper infer the obvious case.
The result is that a test can state a rule once, name it, and reuse it. A matcher defined for a domain object, say a user with a given tier and a name, is just another object, and the documentation is explicit that writing your own matchers is a supported activity rather than a workaround.
Four ways to add it to a project
The repository is set up for four distribution routes, and the examples directory mirrors them so you can see a working integration for each.
Swift Package Manager is the first, and the repository root contains a Package.swift manifest alongside a Package.swift platform compatibility badge. The release note for v9.1.0 mentions visionOS, more architectures and a cleaner SPM setup, which tells you the manifest is actively maintained for the current toolchain.
CocoaPods is the second, with an OCHamcrest.podspec at the root and a CocoaPods version badge. Carthage is the third, with a Cartfile.project file and a Carthage compatible badge. The fourth is a prebuilt framework, for teams that want a binary rather than a source build, and the fifth route is building your own.
The Examples directory carries a project for each combination that matters: a Mac example and an iOS example, each in CocoaPods, framework and Swift Package Manager flavours, plus a custom date matcher example built with CocoaPods that shows what writing your own matcher looks like in a real project.
The repository layout also shows the internals: Sources/ holds the implementation, Tests/ the test suite, Resources/ assets, and there is a CHANGELOG.md, a Gemfile, an mdsnippets.json file that drives the generated documentation snippets, a .slather.yml for coverage reporting, and a merge_dependabot.sh script for dependency updates.
A release history dominated by toolchain fixes
The maintenance picture is the part to weigh before adopting. The last push to the repository was on 2026-06-18, and the repository is not archived. The recent releases are v9.1.1 with the note Mac architectures fix from 2025-02-24, v9.1.0 covering visionOS, more architectures and a cleaner SPM from 2025-01-26, and v9.0.3 fixing OCMockito CocoaPods for Xcode 15 from 2023-10-08.
Two things stand out. First, the project is not dormant, since the June 2026 push postdates every tag, so work is happening on the branch even though no new release has been cut. Second, the notes are almost entirely about platforms and toolchains rather than about new matchers, which is what you would expect from a library whose job is stable and whose users are blocked by what Xcode and the OS support.
That has a practical consequence for adoption. The version you can get from a package manager may be older than the source, and the gap is most likely to matter on a new architecture or a new Xcode. The v9.1.1 Mac architectures fix is exactly the kind of change a consumer on an unusual platform needs, and it was released in February 2025, so anyone on a recent Mac tooling setup should check whether the packaged version they get includes it.
The licence is BSD-2-Clause, with the licence text in LICENSE.txt at the root, and the project is hosted under the hamcrest organisation alongside the other language ports.
Where the matcher approach is the wrong fit
The first limitation is that the readability argument is weaker than it looks in a large test suite. A team that already writes its Objective-C tests with XCTest assertions in a consistent style gets a real benefit from OCHamcrest only where the assertion is about a rule rather than a value. Comparing two objects for equality does not need a matcher, and replacing XCTAssertEqualObjects with assertThat and equalTo there adds a vocabulary without adding clarity. The payoff appears when a test asserts several conditions about a collection, or a property of a domain object, and the plain assertion syntax would need a chain of boolean subexpressions that say nothing on failure.
The second limitation is a missing diagnostic detail. The library's model is a rule object, and the failure output tells you which matcher failed and why, but it does not tell you which of the four conditions in a long allOf chain was the one that broke. A composite matcher produces a compound failure description, and the developer still has to read it. For a test that asserts on a large object graph, the diagnosis cost can exceed the cost of the assertion you replaced.
The third is language. OCHamcrest is an Objective-C module, and the documentation has a section titled What About Swift, which is a signal that Swift users are a distinct case rather than the primary audience. The main repository is Objective-C with a Swift Package Manager manifest for consumption, and a Swift-specific matcher library is a separate project.
The real alternative is XCTest itself, and the difference in approach is worth being concrete about. XCTest assertions are functions with the expected value baked in as a parameter, so expressing a rule about a value means writing that rule inline, and the failure message is generated by the assertion function. OCHamcrest inverts that: the rule is an object, so it can be stored, passed around, negated, combined and reused, and the failure message comes from the matcher rather than the assertion. For a codebase with rich domain objects, custom matchers and shared test fixtures, that inversion pays. For a codebase that asserts on strings and numbers, XCTest is shorter and there is nothing in the matcher model it cannot express.
Editorial conclusion
Adopt OCHamcrest when you write Objective-C tests where plain XCTAssertEqualObjects cannot express the rule you care about, and when you are willing to learn the matcher vocabulary. Do not adopt it for a Swift-only codebase, since the documentation addresses Swift separately, and do not adopt it because a failure message needs to be prettier, since the readability gain comes from the matcher vocabulary rather than the output format. Before you add the dependency, check that the version you pin supports the architectures your CI builds, since the release history is dominated by architecture and toolchain fixes such as the Mac architectures fix in v9.1.1, and confirm the license file matches what you expect for a BSD-2-Clause library.
Frequently asked questions
What is OCHamcrest?
OCHamcrest is an Objective-C module providing a library of matcher objects for declaring rules to check whether an object matches them, plus a framework for writing your own matchers. It is the Objective-C port of Hamcrest and is distributed under the BSD-2-Clause licence.
How do I add OCHamcrest to an Xcode project?
The documentation lists four routes: Swift Package Manager, with a Package.swift manifest at the repository root, CocoaPods with an OCHamcrest.podspec, Carthage with a Cartfile.project, and a prebuilt framework, with building your own as a further option. The Examples directory contains a working project for each combination.
How do I write a custom matcher in OCHamcrest?
Writing your own matchers is a supported activity, and the library provides a framework for it. The repository includes an Examples project for a custom date matcher built with CocoaPods, which shows the pattern in a real project, and the matcher arguments accept other matchers, so your matcher can itself be composed with the predefined ones.
What is the difference between contains and containsIn in OCHamcrest?
Across the collection family the plain form takes a collection argument and the In form takes a list, so contains matches exactly the entire collection while containsIn matches against items given as a list. The same convention applies to hasItems and hasItemsIn, and to onlyContains and onlyContainsIn.
Is OCHamcrest still maintained?
The repository is not archived and the last push was on 2026-06-18, though the most recent tagged release is v9.1.1 from 2025-02-24. The recent release notes focus on platforms and toolchains, including a Mac architectures fix in v9.1.1 and visionOS support with more architectures in v9.1.0.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hamcrest-ochamcrest)Was this useful?