Quality Coding
Shares

What Benefits Are There in the New, Improved OCHamcrest?

Shares

Many of you already use OCHamcrest, the matcher library I initially ported from Java back in 2008. I’m writing today to announce OCHamcrest 5.0.0, and to explain what you’ll get out of upgrading.

But first, what does it mean to jump from v4.3.2 to v5.0.0?

OCHamcrest

Semantic Versioning

OCHamcrest follows Semantic Versioning. A change in the major version number (in this case, going from 4.x to 5.x) doesn’t necessarily mean there’s a shiny new feature. Rather, it communicates that I’ve made changes that may break backwards compatibility for you.

In other words, the major version change isn’t marketing. It means that if you’re currently using OCHamcrest, dropping in v5.0.0 may require changes in your code. I say “may” because it depends on your code. Most of you should be able to upgrade without any changes. (But I’m going to urge you to make a simple global edit of the HC_SHORTHAND define.)

I bump the major version number when I delete any public APIs. The following are gone:

  • Replace equalToBool with isTrue / isFalse
  • Rename containsString to containsSubstring
  • Rename assertThatAfter / futureValueOf to assertWithTimeout / thatEventually
  • Replace HC_testFailureHandlerChain() with [HCTestFailureReporterChain reporterChain]

These APIs were already marked as deprecated, so if you see any warnings about deprecated code, your code isn’t ready for v5.0.0. Clean those up first.

But hey, if I’m breaking backwards compatibility anyway, I may as well take advantage of the opportunity to introduce a bigger change…

No more #define HC_SHORTHAND

Because Objective-C lives in a C world, there are no custom namespaces. We use package prefixes to avoid stepping on each other’s toes. Apple says, “Your own classes should use three letter prefixes.” OCHamcrest actually violates this by using the two-letter prefix HC for its classes. This was either because I didn’t know about Apple’s recommendation when I started, or they hadn’t spelled it out yet.

Prefixes are necessary for anything in the global namespace. Among other things, this applies to functions — which OCHamcrest makes extensive use. For example, the factory function that creates an HCIsEqual object is HC_equalTo. (I did use a three-letter prefix “HC_” with that unusual underscore for OCHamcrest functions.)

The matcher assertion HC_assertThat(actual, HC_equalTo(expected)) works, but the prefixes make it less appealing than the Java version. So I decided to offer an optional “shorthand” version. A #define HC_SHORTHAND before importing OCHamcrest.h created replacement macros so that we Objective-C folks can also write assertThat(actual, equalTo(expected)).

But things are changing, and it’s time to switch things around…

What you need to do:

If there are places where you have worked to avoid name clashes by not defining HC_SHORTHAND before importing OCHamcrest.h, you must now explicitly #define HC_DISABLE_SHORT_SYNTAX before the #import. (Hat tip to Quick for illustrating how to do this.)

What you ought to do:

Leaving #defines that are no longer useful clutters your code. I’d replace all occurrences of #define HC_SHORTHAND (followed by newline) with nothing, to delete these lines.

Why?

We are shifting away from a world where a #define can affect the contents of an imported header. Modules and @import aren’t yet available to custom frameworks, but they beckon. To get there and still keep short syntax, I am making short syntax the default.

Matchers that take Objective-C literals

When I first wrote OCHamcrest, Objective-C only had string literals. So any matchers that took variable-length lists were comma-separated, ending with nil. And for numeric types, there’s an entire set of assertThat<Type> and equalTo<Type>, such as assertThatInt and equalToInt.

Again, times have changed. The older assertThats and numeric matchers are still there, but I don’t use them anymore. I use NSNumber literals like @123, or box primitive variables into NSNumbers like @(value). That is, instead of

assertThatInt(actual, equalToInt(123));

I use

assertThat(@(actual), equalTo(@123));

That much came for free when Objective-C added NSNumber literals. But support for variable-length lists as literals required new matchers. So in OCHamcrest 5.0.0, nearly all matchers with variable-length lists have new “In” variants that that take collection literals.

What you can do:

Let’s take anyOf as an example. It basically takes multiple matchers and “or’s” them together. You can continue to use the nil-terminated

anyOf(equalTo(@"FOO"), equalTo(@"BAR"), nil)

But now you can say anyOfIn and specify an array literal:

anyOfIn(@[ equalTo(@"FOO"), equalTo(@"BAR") ])

There is one matcher that takes an NSDictionary instead, and that’s hasEntriesIn. With the old hasEntries, you specify a variable-length list of alternating keys and their value matchers:

hasEntries(@"firstName", equalTo(@"Jon"), @"lastName", equalTo(@"Reid"), nil)

But now you can specify it more naturally:

hasEntriesIn(@{ @"firstName": equalTo(@"Jon"), @"lastName": equalTo(@"Reid") })

Finally, you should know that describedAs doesn’t have an “In” variant.

Better argument handling

So far, I’ve mentioned two facets of my original design for OCHamcrest: short names for factory functions, and variable-length lists terminated with nil.

I implemented the short names with macros. But at the time, I didn’t know how to combine macros with variable-length arguments. So I punted, and did this:

#define anyOf HC_anyOf

No argument handling, just straight replacement of text. But this introduced a problem. HC_assertThat takes two arguments, the actual value and a matcher. I once tried to handle these as macro arguments:

#define assertThat(actual, matcher) HC_assertThat(actual, matcher)

But this didn’t work well, especially with matchers that took variable-length lists. The preprocessor didn’t know what to make of those extra commas. So I backed off and kept assertThat as a simple substitution macro:

#define assertThat HC_assertThat

Fast-forward to OCHamcrest 5.0.0. In the process of writing the new “In” matchers (and having the old variadic matchers call their “In” variants), I learned more about variadic arguments and was able to get them working on macros. This gives you better hinting as you enter OCHamcrest expressions:

OCHamcrest hinting

Better help

Apple’s support for HeaderDoc has waxed and waned. Xcode used to display documentation on commented macros, but this hasn’t worked for some time. But by converting most macros to inline functions, the documentation is restored. Moreover, I’ve updated all the documentation to be more precise. For example:

OCHamcrest Help

Unfortunately, matchers with nil-terminated arguments are still macros, so Xcode doesn’t show their documentation. Of course, it’s all there in the headers, so you can see them that way. Or you can begin adopting the “In” matchers which have full documentation.

OCHamcrest remains valuable for me, and I hope you find it helpful. Besides these, what changes would you like to see? Do you have a matcher wish-list? Click here to leave a comment.

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He’s been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:
Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.

Leave a Comment:

6 comments
Michael Hackett says a couple of years ago

Hi Jon,

Thanks for your continuing work on OCHamcrest! One small comment: `equalToBool` is still useful when you want to compare two `BOOL` variables for equality. You mentioned you prefer to wrap primitives in tests, which is certainly an option here. I’m still torn on that one, though. It’s obviously shorter, but NSNumber is a bit of a black box, and I feel like it’s safer to work with the actual production types in tests (though I have no counter-cases to base that on, maybe that’s just paranoia). In any case, I wonder about the inconsistency with keeping the other primitive-type matchers — or are those marked for future removal as well?

Reply
    Jon Reid says a couple of years ago

    Michael,

    equalToBool(value) was replaced by isTrue() and isFalse(). That’s the reason it went away. Do you still feel a need for equalToBool, given the newer matchers? (Please let me know.)

    Don’t worry, I won’t delete the primitive matchers.

    Reply
Thomas Weisbach says a couple of years ago

I’ve never looked at the Java version of Hamcrest, but I was wondering why there isn’t a simple version of

assertThat(condition)

that doesn’t require specifying true or false. For example, XCTest has

XCTAssert(expression)

.

Reply
    Jon Reid says last year

    Why, what’s wrong with using XCTAssert? Though personally, I use XCTAssertTrue / XCTAssertFalse for checking boolean results.

    Fundamentally, OCHamcrest provides matchers, which are useful in contexts other than assertThat. The main place I use them outside of assertThat is to specify argument matching in OCMockito. You can do the same in OCMock.

    Reply
      Thomas Weisbach says last year

      It’s mainly a stylistic thing. I think it is easier to scan the tests for the asserts if they have the same pattern.

      Btw, is this website supposed to send an email when someone replies to your comment? I was expecting an email notification.

      Reply
        Jon Reid says last year

        Hmm, it used to have a checkbox for email notification. That plugin must be broken, let me look into it.

        Reply
Add Your Reply