Assertions

Assertions are used to confirm that the actual value matches the expected value. They are methods of the Tester\Assert class.

Choose the most suitable assertions. Assert::same($a, $b) is better than Assert::true($a === $b), because it displays a meaningful error message upon failure. In the second case, we only get false should be true which tells us nothing about the contents of the variables $a and $b.

Most assertions can also have an optional description in the $description parameter, which is displayed in the error message if the expectation fails.

Examples assume the following alias is created:

use Tester\Assert;

Assert::same ($expected, $actual, ?string $description=null)

$expected must be identical to $actual. It's the same as the PHP operator ===.

Assert::notSame ($expected, $actual, ?string $description=null)

Opposite of Assert::same(), meaning it's the same as the PHP operator !==.

Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

$expected must be equal to $actual. Unlike Assert::same(), object identity, the order of key ⇒ value pairs in arrays, and marginally different decimal numbers are ignored, which can be changed by setting $matchIdentity and $matchOrder.

The following cases are equal from the perspective of equal(), but not same():

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

But beware, the arrays [1, 2] and [2, 1] are not the same, because only the order of values differs, not the key ⇒ value pairs. The array [1, 2] can also be written as [0 => 1, 1 => 2], and [1 => 2, 0 => 1] will therefore be considered the same.

You can also use so-called Expectations in $expected.

Assert::notEqual ($expected, $actual, ?string $description=null)

Opposite of Assert::equal().

Assert::contains ($needle, string|array $actual, ?string $description=null)

If $actual is a string, it must contain the substring $needle. If it is an array, it must contain the element $needle (compared strictly).

Assert::notContains ($needle, string|array $actual, ?string $description=null)

Opposite of Assert::contains().

Assert::hasKey (string|int $needle, array $actual, ?string $description=null)

$actual must be an array and must contain the key $needle.

Assert::notHasKey (string|int $needle, array $actual, ?string $description=null)

$actual must be an array and must not contain the key $needle.

Assert::true ($value, ?string $description=null)

$value must be true, i.e., $value === true.

Assert::truthy ($value, ?string $description=null)

$value must be truthy, i.e., it satisfies the condition if ($value) ....

Assert::false ($value, ?string $description=null)

$value must be false, i.e., $value === false.

Assert::falsey ($value, ?string $description=null)

$value must be falsey, i.e., it satisfies the condition if (!$value) ....

Assert::null ($value, ?string $description=null)

$value must be null, i.e., $value === null.

Assert::notNull ($value, ?string $description=null)

$value must not be null, i.e., $value !== null.

Assert::nan ($value, ?string $description=null)

$value must be Not a Number. For testing NAN values, use exclusively Assert::nan(). The NAN value is very specific, and assertions like Assert::same() or Assert::equal() may behave unexpectedly.

Assert::count ($count, Countable|array $value, ?string $description=null)

The number of elements in $value must be $count. It's the same as count($value) === $count.

Assert::type (string|object $type, $value, ?string $description=null)

$value must be of the given type. As $type we can use a string:

  • array
  • list – array indexed according to an ascending series of numeric keys from zero
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • class name or directly an object, then $value instanceof $type must be

Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)

When calling $callable, an exception of class $class must be thrown. If we specify $message, the exception message must also match the pattern. And if we specify $code, the codes must also strictly match.

For example, the following test will fail because the exception message does not match:

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is too low',
);

Assert::exception() returns the thrown exception, allowing you to test a nested exception as well.

$e = Assert::exception(
	fn() => throw new MyException('Something is wrong', 0, new RuntimeException),
	MyException::class,
	'Something is wrong',
);

Assert::type(RuntimeException::class, $e->getPrevious());

Assert::error (string $callable, int|string|array $type, ?string $message=null)

Checks that the function $callable generated the expected errors (ie warnings, notices, etc). As $type specify one of the E_... constants, for example E_WARNING. And if we specify $message, the error message must also match the pattern. For example:

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

If the callback generates more errors, we must expect them all in the exact order. In this case, pass an array in $type:

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

If you specify the class name as $type, it behaves the same as Assert::exception().

Assert::noError (callable $callable)

Checks that the function $callable did not generate any warning, error or exception. It is useful for testing pieces of code where there is no other assertion.

Assert::match (string $pattern, $actual, ?string $description=null)

$actual must match the pattern $pattern. We can use two variants of patterns: regular expressions or wildcards.

If we pass a regular expression as $pattern, we must use ~ or # to delimit it. Other delimiters are not supported. For example, a test where $var must contain only hexadecimal digits:

Assert::match('#^[0-9a-f]$#i', $var);

The second variant is similar to comparing ordinary strings, but we can use various wildcards in $pattern:

  • %a% one or more of anything except line ending characters
  • %a?% zero or more of anything except line ending characters
  • %A% one or more of anything including line ending characters
  • %A?% zero or more of anything including line ending characters
  • %s% one or more whitespace characters except line ending characters
  • %s?% zero or more whitespace characters except line ending characters
  • %S% one or more characters except whitespace characters
  • %S?% zero or more characters except whitespace characters
  • %c% a single character of any sort (except line ending)
  • %d% one or more digits
  • %d?% zero or more digits
  • %i% signed integer value
  • %f% floating-point number
  • %h% one or more hexadecimal digits
  • %w% one or more alphanumeric characters
  • %% one % character

Examples:

# Again, test for a hexadecimal number
Assert::match('%h%', $var);

# Generalization of file path and line number
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::matchFile (string $file, $actual, ?string $description=null)

This assertion is identical to Assert::match(), but the pattern is loaded from the file $file. This is useful for testing very long strings. The test file remains clear.

Assert::fail (string $message, $actual=null, $expected=null)

This assertion always fails. Sometimes it's just useful. Optionally, we can specify the expected and actual value.

Expectations

When we want to compare more complex structures with non-constant elements, the assertions mentioned above may not be sufficient. For example, we are testing a method that creates a new user and returns its attributes as an array. We do not know the value of the password hash, but we know that it must be a hexadecimal string. And about the next element, we only know that it must be an object DateTime.

In these situations, we can use Tester\Expect inside the $expected parameter of the Assert::equal() and Assert::notEqual() methods, using which the structure can be easily described.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # we expect an integer
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # we expect a string matching the pattern
	'created_at' => Expect::type(DateTime::class), # we expect an instance of the class
], User::create(123, 'milo', 'RandomPaSsWoRd'));

With Expect, we can perform almost the same assertions as with Assert. Thus, methods Expect::same(), Expect::match(), Expect::count(), etc. are available to us. In addition, we can chain them:

Expect::type(MyIterator::class)->andCount(5);  # we expect MyIterator and the number of elements is 5

Alternatively, we can write our own assertion handlers.

Expect::that(function ($value) {
	# return false if the expectation fails
});

Exploring Failed Assertions

When an assertion fails, Tester prints what the error is. If we compare complex structures, Tester creates dumps of the compared values and saves them to the output directory. For example, if the fictional test Arrays.recursive.phpt fails, the dumps will be stored as follows:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # actual value
	│   └── Arrays.recursive.expected  # expected value
	│
	└── Arrays.recursive.phpt          # failing test

We can change the directory name via Tester\Dumper::$dumpDir.