Expectations

Overview

In addition to assertions, Pest offers you a set of expectations. These functions let you test your values against certain conditions. This API is inspired by Jest. Expectations also allow you to write your tests like you would a natural sentence:

1test('expect true to be true', function () {
2 // assertion
3 $this->assertTrue(true);
4 
5 // expectation
6 expect(true)->toBe(true);
7});

expect($value)

Start the expectation by passing your value to this function. Then, you can chain your check(s):

1expect($value)->// chain your checks here

Available Expectations

toBe()

Asserts that two variables have the same type and value. Used on objects, it asserts that the two variables reference the same object:

1expect($color)->toBe('pink');
2 
3expect($user)->toBe($user);

toBeEmpty()

Asserts that the value is empty:

1expect($lastSeen)->toBeEmpty();

toBeTrue()

Asserts that the value is true:

1expect($published)->toBeTrue();

toBeTruthy()

Asserts that the value is truthy:

1expect(1)->toBeTruthy();
2expect('1')->toBeTruthy();

toBeFalse()

Asserts that the value is false:

1expect($archived)->toBeFalse();

toBeFalsy()

Asserts that the value is falsy:

1expect(0)->toBeFalsy();
2expect('')->toBeFalsy();

toBeGreaterThan($expected)

Asserts that the value is greater than the expected one:

1expect($age)->toBeGreaterThan(20);

toBeGreaterThanOrEqual($expected)

Asserts that the value is greater than or equal to the expected one:

1expect($age)->toBeGreaterThanOrEqual(21);

toBeLessThan($expected)

Asserts that the value is lesser than the expected one:

1expect($count)->toBeLessThan(3);

toBeLessThanOrEqual($expected)

Asserts that the value is lesser than or equal to the expected one:

1expect($count)->toBeLessThanOrEqual(2);

toContain($needles)

Asserts that all given needles are elements of the value:

1expect("Hello World")->toContain('Hello');
2expect([1, 2, 3, 4])->toContain(2, 4);

toHaveCount(int $count)

Asserts that the $count matches the number of elements of $value:

1expect($dozen)->toHaveCount(12);

toHaveProperty(string $name, $value = null)

Asserts that the $value contains the property $name:

1expect($user)->toHaveProperty('name');
2expect($user)->toHaveProperty('name', 'Nuno');

toHaveProperties(iterable $name)

Asserts that the $value contains the provided properties $names:

1expect($user)->toHaveProperties(['name', 'email']);

toMatchArray($array)

Asserts that the $value array matches the given $array subset.

1$user = [
2 'id' => 1,
3 'name' => 'Nuno',
4 'email' => 'enunomaduro@gmail.com',
5];
6 
7expect($user)->toMatchArray([
8 'email' => 'enunomaduro@gmail.com',
9 'name' => 'Nuno'
10]);

toMatchObject($object)

Asserts that the $value object matches a subset of the properties of a given $object:

1$user = new stdClass();
2$user->id = 1;
3$user->email = 'enunomaduro@gmail.com';
4$user->name = 'Nuno';
5 
6expect($user)->toMatchObject([
7 'email' => 'enunomaduro@gmail.com',
8 'name' => 'Nuno'
9]);

toEqual($expected)

Asserts that two variables have the same value.

1expect($title)->toEqual('Hello World');

toEqualCanonicalizing($expected)

Asserts that two variables have the same value. The contents of $value and $expected are canonicalized before they are compared. For instance, when the two variables are arrays, then these arrays are sorted before they are compared. When they are objects, each object is converted to an array containing all private, protected, and public attributes.

1expect([4, 2, 1])->toEqualCanonicalizing([2, 4, 1]);
2 
3// this is the equivalent of:
4expect([1, 2, 4])->toEqual([1, 2, 4]);

toEqualWithDelta($expected, float $delta)

Asserts that the absolute difference between $value and $expected is lower than $delta:

1// Pass
2expect(14)->toEqualWithDelta(10, 5);
3 
4// Fail
5expect(14)->toEqualWithDelta(10, 0.1);

toBeIn()

Asserts that the value is one of the given values.

1expect($response->httpCode)->toBeIn([200, 301, 302]);

toBeInfinite()

Asserts that the value is infinite:

1expect($universe)->toBeInfinite();

toBeInstanceOf($class)

Asserts that the value is an instance of $class:

1expect($user)->toBeInstanceOf(User::class);

toBeArray()

Asserts that the value is an array:

1expect($vegetables)->toBeArray();

toBeBool()

Asserts that the value is of type bool:

1expect($isActive)->toBeBool();

toBeCallable()

Asserts that the value is of type callable:

1expect($controller)->toBeCallable();

toBeFloat()

Asserts that the value is of type float:

1expect($height)->toBeFloat();

toBeInt()

Asserts that the value is of type integer:

1expect($count)->toBeInt();

toBeIterable()

Asserts that the value is of type iterable:

1expect($value)->toBeIterable();

toBeNumeric()

Asserts that the value is of type numeric:

1expect($age)->toBeNumeric();

toBeObject()

Asserts that the value is of type object:

1expect($post)->toBeObject();

toBeResource()

Asserts that the value is of type resource:

1expect($resource)->toBeResource();

toBeScalar()

Asserts that the value is of type scalar:

1expect($scalar)->toBeScalar();

toBeString()

Asserts that the value is of type string:

1expect($string)->toBeString();

toBeJson()

Asserts that the value is a JSON string:

1expect('{"hello":"world"}')->toBeJson();

toBeNan()

Asserts that the value is not a number (NaN):

1expect($nan)->toBeNan();

toBeNull()

Asserts that the value is null:

1expect(null)->toBeNull();

toHaveKey(string $key)

Asserts that the value array contains the provided $key:

1expect($array)->toHaveKey('key-a');

You may pass a second parameter to assert that the value at the given key is equal to something:

1expect(['foo' => 'bar'])->toHaveKey('foo', 'bar');

This expectation also supports dot notation for reaching deeper into nested arrays:

1expect(['user' => ['nuno' => 'maduro']])->toHaveKey('user.nuno');
2expect(['user' => ['nuno' => 'maduro']])->toHaveKey('user.nuno', 'maduro');

toHaveKeys(array $keys)

Asserts that the value array contains the provided $keys:

1expect($array)->toHaveKeys(['key-a', 'key-b']);

This expectation also supports dot notation for reaching deeper into nested arrays:

1expect(['user' => ['nuno' => 'maduro', 'luke' => 'downing']])->toHaveKeys(['user.nuno', 'user.luke']);

toHaveLength(int $number)

Asserts that the $number matches the $value's string length, or number of elements of the iterable $value:

1expect('pest')->toHaveLength(4);
2expect(['Nuno', 'Maduro'])->toHaveLength(2);
3expect('')->not->toHaveLength(5);
4expect(true)->toHaveLength(4); //throws a `BadMethodCallException` when the expectation value type is not supported.

toBeDirectory()

Asserts that the value is a directory:

1expect($dir)->toBeDirectory();

toBeReadableDirectory()

Asserts that the value is a directory and is readable.

1expect($dir)->toBeReadableDirectory();

toBeWritableDirectory()

Asserts that the value is a directory and is writable:

1expect($dir)->toBeWritableDirectory();

toStartWith(string $expected)

Asserts that the value starts with the provided string:

1expect($content)->toStartWith('Hello');

toThrow()

Asserts that a closure throws an exception class, exception message, or the combination of both.

1test('it throws the desired Exception class', function () {
2 expect(fn() => throw new Exception('Something happened.'))->toThrow(Exception::class);
3});
1test('it throws an exception with desired message', function () {
2 expect(fn() => throw new Exception('Something happened.'))->toThrow('Something happened.');
3});
1test('it throws the desired Exception class with the desired message', function () {
2 expect(fn() => throw new Exception('Something happened.'))->toThrow(Exception::class, 'Something happened.');
3});

You may assert more than one exception per test:

1test('it asserts two exceptions with their specific messages', function () {
2 expect(fn() => throw new Exception('Error 1'))->toThrow(Exception::class, 'Error 1');
3 
4 expect(fn() => throw new Exception('Error 2'))->toThrow(Exception::class, 'Error 2');
5});

It is also possible to use not() modifier together with toThrow():

1test('it does not throw an Exception', function () {
2 expect(fn ($x, $y) => $x + $y)->not->toThrow(Exception::class);
3});

toMatch(string $expression)

Asserts that the value matches a regular expression:

1expect('Hello World')->toMatch('/^hello wo.*$/i');

toEndWith(string $expected)

Asserts that the value ends with the provided string:

1expect($content)->toEndWith('World');

toMatchConstraint(Constraint $constraint)

Asserts that the value matches a specified PHPUnit constraint:

1use PHPUnit\Framework\Constraint\IsTrue;
2 
3expect(true)->toMatchConstraint(new IsTrue());

Asserts that the value matches a complex group of constraints:

1use PHPUnit\Framework\Constraint\IsFalse;
2use PHPUnit\Framework\Constraint\IsType;
3 
4expect(true)->toMatchConstraint(
5 $this->logicalAnd(
6 $this->logicalNot(new IsFalse()),
7 new IsType(IsType::TYPE_BOOL)
8 )
9);

Asserts that the value matches a custom constraint:

1expect('https://google.com')->toMatchConstraint(new IsValidUrlConstraint());
2class IsValidUrlConstraint extends \PHPUnit\Framework\Constraint\Constraint
3{
4 public function toString(): string
5 {
6 return 'is a valid url';
7 }
8 protected function matches($other): bool
9 {
10 if (! is_string($other)) {
11 return false;
12 }
13 return preg_match(
14 Symfony\Component\Validator\Constraints\UrlValidator::PATTERN,
15 $other
16 ) > 0;
17 }
18}

Custom constraints should extend PHPUnit\Framework\Constraint\Constraint, and provide a matches() and toString() method, and optionally override the evaluate() method.

and($value)

Pass a new value to the and function to chain multiple expectations in a single test:

1expect($id)->toBe(14)->and($name)->toBe('Nuno');

dd()

Use the dd method to dumps the current expectation value and ends the script:

1expect(14)->dd(); // 14
2 
3expect([1, 2])->sequence(
4 fn ($number) => $number->toBe(1),
5 fn ($number) => $number->dd(), // 2
6);

each()

Use the each modifier to create an expectation on each item of the given iterable:

1expect([1, 2, 3])->each->toBeInt();
2expect([1, 2, 3])->each->not->toBeString();
3 
4expect([1, 2, 3])->each(fn ($number) => $number->toBeLessThan(4));

json()

Pass a JSON string to the json method to assert it is a valid JSON and chain other expectations:

1expect('{"name":"Nuno","credit":1000.00}')
2 ->json()
3 ->toHaveCount(2)
4 ->name->toBe('Nuno')
5 ->credit->toBeFloat();

match()

The match method executes the callback of the first key that matches the first argument given to the method:

1expect('pest')
2 ->match(true, [
3 true => fn ($value) => $value->toEqual('pest'),
4 false => fn ($value) => $value->not->toEqual('pest')
5 ]);

If you just want to check that the expected value is equal to the value of the matching key, you can pass the expected value directly instead of using a callback:

1expect('pestphp')
2 ->match('twitter', [
3 'twitter' => 'pestphp',
4 'website' => 'pestphp.com'
5 ]);

not()

Use the not modifier before a check to invert it:

1expect($id)->not->toBe(14);

ray()

Use the ray method to debug the current expectation value with myray.app:

1expect(14)->ray(); // 14
2 
3expect([1, 2])->sequence(
4 fn ($number) => $number->toBe(1),
5 fn ($number) => $number->ray(), // 2
6);

sequence()

Use the sequence method to specify a sequential set of expectations for each item of the given iterable:

1expect([1, 2, 3])->sequence(
2 fn ($number) => $number->toBe(1),
3 fn ($number) => $number->toBe(2),
4 fn ($number) => $number->toBe(3),
5);

You can also use the sequence method with associative iterables. Each closure receives the value as an expectation for the first argument, and the key as an expectation for the second argument:

1expect(['hello' => 'world', 'foo' => 'bar', 'john' => 'doe'])->sequence(
2 fn ($value, $key) => $value->toEqual('hello'),
3 fn ($value, $key) => $key->toEqual('foo'),
4 fn ($value, $key) => $value->toBeString(),
5);

If you just want to check that each value in the iterable is equal to another value, you can pass the expected value directly instead of using a closure:

1expect(['foo', 'bar', 'baz'])->sequence('foo', 'bar', 'baz');

when()

The when method will execute the given callback when the first argument given to the method evaluates to true:

1expect($user)
2 ->when(true, fn ($user) => $user->verified->toBeTrue())
3 ->first_name->toEqual('Nuno');

For the inverse of when, see the unless method.

unless()

The unless method will execute the given callback unless the first argument given to the method evaluates to true:

1expect($user)
2 ->unless(false, fn ($user) => $user->verified->toBeTrue())
3 ->first_name->toEqual('Nuno');

Higher Order Expectations

NOTE: You cannot call methods and properties on your expectation value that conflict with methods in the Expectation API using Higher Order Expectations.

You may create expectations on methods or properties of the original expectation value. As an example, imagine you're testing that a User can be created within your system. You might want to test that a variety of attributes have been stored correctly:

1expect($user->first_name)->toEqual('Nuno');
2expect($user->last_name)->toEqual('Maduro');
3expect($user->withTitle('Mr'))->toEqual('Mr Nuno Maduro');

With higher order expectations, you can refactor that test to:

1expect($user)
2 ->first_name->toEqual('Nuno')
3 ->last_name->toEqual('Maduro')
4 ->withTitle('Mr')->toEqual('Mr Nuno Maduro');

You may also access array keys to perform expectations on them:

1expect(['name' => 'Nuno', 'companies' => ['Pest', 'Laravel']])
2 ->name->toEqual('Nuno')
3 ->companies->toHaveCount(2)->each->toBeString

Pest takes care of retrieving the property or calling the method on the value under test.

Higher order expectations can be used with all of Pest's expectations. Which means you can create tests that are both powerful and elegant - even creating further higher order expectations within each() and sequence() closures:

1expect($user)
2 ->posts
3 ->not->toBeEmpty
4 ->toHaveCount(2)
5 ->sequence(
6 fn ($post) => $post->title->toEqual('My first post!'),
7 fn ($post) => $post->title->toEqual('My second post')
8 );

Your Higher Order Expectations can reach as deep into an object or associative array as you like. Once you perform one or more of Pest's expectations, the expectation's scope will reset to the initial value again:

1expect($user)
2 ->companies->first()->owner->toBeInstanceOf(User::class)->not->toEqual($user)
3 ->name->toEqual('Nuno');

Custom Expectations

You can use expect()->extend() to add your own expectations to Pest. For example, let's say that you're testing a number utility library and you're frequently asserting that numbers appear within particular ranges of other numbers. You could abstract that into a toBeWithinRange expectation:

1expect()->extend('toBeWithinRange', function ($min, $max) {
2 return $this->toBeGreaterThanOrEqual($min)
3 ->toBeLessThanOrEqual($max);
4});
5 
6test('numeric ranges', function () {
7 expect(100)->toBeWithinRange(90, 110);
8});

Those custom expectations may be also placed in your tests/Pest.php file.


Next section: Setup And Teardown →