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 zerobool
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
.