hamcrest/hamcrest-php
Official PHP port of Hamcrest matchers for expressive assertions in tests. Use MatcherAssert::assertThat() or convenient global functions (assertThat, equalTo, is, both/andAlso, either/orElse) to build readable, composable matchers with PHP-friendly typing.
composer require hamcrest/hamcrest-php
assertThat sugar):
use Hamcrest\Util;
Util::registerGlobalFunctions();
assertThat('foo', equalTo('foo'));
$response = $this->get('/api/users/1');
$expected = [
'id' => 1,
'name' => 'John Doe',
'email' => 'john@example.com'
];
assertThat(json_decode($response->getContent(), true),
arrayContaining([
'id' => equalTo(1),
'name' => equalToIgnoringCase('john doe'),
'email' => matchesPattern('/^[^\s@]+@[^\s@]+\.[^\s@]+$/')
])
);
Where to Look First:
equalTo, not)Workflow: Break complex validations into reusable matchers.
// Reusable matcher for valid email
$validEmail = matchesPattern('/^[^\s@]+@[^\s@]+\.[^\s@]+$/');
// Usage
assertThat($user['email'], $validEmail);
assertThat($users, everyItem(hasKey('email', $validEmail)));
Pattern: Combine matchers for nested validations.
assertThat($order,
allOf(
hasKey('items', arrayWithSize(greaterThan(0))),
hasKey('total', greaterThanOrEqualTo(100)),
hasKey('status', equalTo('completed'))
)
);
Extension Point: Create domain-specific matchers.
use Hamcrest\Matcher;
class ValidSlug extends Matcher {
public function matches($slug) {
return preg_match('/^[a-z0-9-]+$/', $slug);
}
public function describeTo($description) {
$description->appendText('a valid slug (alphanumeric and hyphens)');
}
}
// Usage
assertThat($post['slug'], new ValidSlug());
Tip: Use MatcherAssert for programmatic assertions.
use Hamcrest\MatcherAssert;
public function testUserValidation() {
$user = ['name' => 'Alice', 'age' => 30];
MatcherAssert::assertThat(
$user,
allOf(
hasKey('name', notEmptyString()),
hasKey('age', integerValue())
)
);
}
Pattern: Parameterize matchers for test suites.
$data = [
['input' => 'foo', 'expected' => 'FOO'],
['input' => 'bar', 'expected' => 'BAR'],
];
foreach ($data as $test) {
assertThat(
strtoupper($test['input']),
equalTo($test['expected'])
);
}
Use Case: Validate exceptions with Hamcrest.
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessageThat(
containsString('Invalid input')
);
Global Functions Not Auto-Loaded
assertThat() fails with undefined function unless Util::registerGlobalFunctions() is called.bootstrap.php or test base class:
Util::registerGlobalFunctions();
Case Sensitivity in equalTo
equalTo('foo') fails for 'Foo' (use equalToIgnoringCase).equalToIgnoringCase for user-generated strings.Array Order Sensitivity
arrayContaining([1, 2]) fails for [2, 1] (use arrayContainingInAnyOrder).containsInAnyOrder for unordered collections.Null Handling Quirks
nullValue() matches null, but notNullValue() fails for 0, false, or ''.is(notNullValue()) explicitly.PHP 8+ Type Safety
public function matches(mixed $value): bool { ... }
Double Inclusion Warnings
if (!function_exists('assertThat')) {
Util::registerGlobalFunctions();
}
Descriptive Failures
describedAs() to customize error messages:
assertThat($user['email'], describedAs('valid email', $validEmail));
MatcherAssert Count
tearDown():
$this->addToAssertionCount(MatcherAssert::getCount());
MatcherAssert::resetCount();
XML Matchers
hasXPath() for complex XML validation:
assertThat($dom, hasXPath('//user[@active="true"]', 1));
Performance
allOf/anyOf can slow tests.$matcher = allOf(hasValue(1), hasValue(2));
foreach ($data as $item) {
assertThat($item, $matcher);
}
Custom Matcher Traits
trait Validatable {
abstract public function validate($value);
public function matches($value) {
return $this->validate($value);
}
}
Matcher Factories
function rangeMatcher($min, $max) {
return allOf(greaterThanOrEqualTo($min), lessThanOrEqualTo($max));
}
// Usage: assertThat($age, rangeMatcher(18, 99));
Integration with Laravel
FormRequest validation:
public function rules() {
return [
'email' => ['required', 'string', new ValidEmailMatcher()]
];
}
Mocking with Hamcrest
$mock->expects($this->once())
->method('process')
->with($this->callback(function ($data) {
return assertThat($data, hasKey('status', equalTo('pending')));
}));
How can I help you explore Laravel packages today?