Assertions

Aassertions are used to assert that an actual value matches an expected value. They are methods of the Tester\Assert.

Choose the most accurate assertions. Is better Assert::same($a, $b) than Assert::true($a === $b) because it displays meaningful error message on failure. In the second case we get false should be true only and it says nothing about $a and $b variables contents.

Most assertions can also have an optional $description that appears in the error message if the expectation fails.

Examples assume the following class alias is defined:

use Tester\Assert;

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

$expected must be the same as $actual. It is the same as PHP operator ===.

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

Opposite to Assert::same(), so it is the same as PHP operator !==.

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

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

The following cases are identical from the point of view of equal(), but not for same():

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

However, beware, the array [1, 2] and [2, 1] are not equal, 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 therefore [1 => 2, 0 => 1] will be considered equal.

You can also use the so-called expectations in $expected.

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

Opposite to 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 (it is compared strictly).

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

Opposite to 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, so $value === true.

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

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

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

$value must be false, so $value === false.

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

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

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

$value must be null, so $value === null.

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

$value must not be null, so $value !== null.

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

$value must be Not a Number. Use only the Assert::nan() for NAN testing. NAN value is very specific and assertions Assert::same() or Assert::equal() can behave unpredictably.

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

Number of elements in $value must be $count. So the same as count($value) === $count.

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

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

  • array
  • list – array indexed in ascending order of numeric keys from zero
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • class name or object directly then must pass $value instanceof $type

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

On $callable invocation an exception of $class instance must be thrown. If we pass $message, the message of the exception must match. And if we pass $code, code of the exception must be the same.

For example, this test fails because message of the exception does not match:

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

The Assert::exception() returns a thrown exception, so you can test a nested exception.

$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 $callable invocation generates the expected errors (ie warnings, notices, etc.). As $type we specify one of the constants E_..., for example E_WARNING. And if pass $message, the error message must also match pattern. For example:

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

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

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

If $type is class name, this assertion behaves same as Assert::exception().

Assert::noError(callable $callable)

Checks that the function $callable does not throw any PHP warning/notice/error or exception. It is useful for testing a piece of code where is no other assertion.

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

$actual must match to $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 test where $var must contain only hexadecimal digits:

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

The other variant is similar to string comparing but we can use some wild chars in $pattern:

  • %a% one or more of anything except for the end of line characters
  • %a?% zero or more of anything except for the end of line characters
  • %A% one or more of anything including the end of line characters
  • %A?% zero or more of anything including the end of line characters
  • %s% one or more white space characters except for the end of line characters
  • %s?% zero or more white space characters except for the end of line characters
  • %S% one or more of characters except for the white space
  • %S?% zero or more of characters except for the white space
  • %c% a single character of any sort (except for the end of line)
  • %d% one or more digits
  • %d?% zero or more digits
  • %i% signed integer value
  • %f% floating point number
  • %h% one or more HEX digits
  • %w% one or more alphanumeric characters
  • %% one % character

Examples:

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

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

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

The assertion is identical to Assert::match() but the pattern is loaded from $file. It is useful for very long strings testing. Test file stands readable.

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

This assertion always fails. It is just handy. We can optionally pass expected and actual values.

Expectations

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

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

use Tester\Expect;

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

With Expect, we can make almost the same assertions as with Assert. So we have methods like Expect::same(), Expect::match(), Expect::count(), etc. In addition, we can chain them like:

Expect::type(MyIterator::class)->andCount(5);  # we expect MyIterator and items count is 5

Or, we can write own assertion handlers.

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

Failed Assertions Investigation

The Tester shows where the error is when an assertion fails. When we compare complex structures, the Tester creates dumps of compared values and saves them into directory output. For example when imaginary test Arrays.recursive.phpt fails the dumps will be saved as follows:

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

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