Write Deterministic PHPUnit Assertions with Mockery::capture
Source: Dev.to
Problem
When a method internally generates a random value, its return value becomes unpredictable, making it hard to write deterministic assertions in tests.
class Hash
{
public function make($data): string
{
return hash_hmac('sha256', $data, false);
}
}
class RandomHash
{
public function __construct(public Hash $hash)
{
}
/**
* @throws \Exception
*/
public function hash(): string
{
$random = md5(random_int(1, 10));
return $this->hash->make($random);
}
}
Because random_int returns a different value on each call, the result of hash() changes each time, so a test cannot assert a fixed expected value.
class RandomHashTest extends TestCase
{
public function test_mockery_capturing_arguments(): void
{
$hash = new Hash();
$randomHash = new RandomHash($hash);
// no way to write a definitive assertion here
$actual = $randomHash->hash();
}
}
Solution
Mockery’s capturing arguments feature can store the arguments passed to a method call. By combining it with passthru() (which lets the original method execute normally), you can capture the intermediate random value and then compute the expected result.
First, install Mockery as a development dependency:
composer require --dev mockery/mockery
Example Test
use Mockery;
use PHPUnit\Framework\TestCase;
class RandomHashTest extends TestCase
{
/**
* @throws \Exception
*/
public function test_mockery_capturing_arguments(): void
{
// Create a spy for the Hash class
$hash = Mockery::spy(new Hash());
// Capture the intermediate random value; passthru lets make() run normally
$hash->allows('make')
->with(Mockery::capture($random))
->passthru();
$randomHash = new RandomHash($hash);
$actual = $randomHash->hash();
// Now $random is known, so we can compute the expected value
self::assertEquals((new Hash)->make($random), $actual);
}
}