You're already feeling confident with your test skills and all your teammates envy you talking about mocks and stubs. But now you're faced with a new challenge, you only need to verify some properties of an argument, otherwise your tests will fail miserably.
Sunny day starts
Your job was to implement a simple user registration feature. As a good programmer you planned this to use the Repository
pattern so you could separate the persistence layer and mock it in unit tests if necessary.
User class
Couldn't be simpler, right?
final class User
{
public function __construct(
private string $id,
private string $name)
{
}
public function getId(): string
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
}
UserRepository
Not doing any persistence, but let's pretend it is.
final class UserRepository
{
public function save(User $user): void
{
echo sprintf('Persisted user with id %s and name %s', $user->getId(), $user->getName());
}
}
UserService
Now we have some plot twist! Some crazy requirements arrived from above and it says the id
field should be randomic. Well, there's a lot of ways to achieve this so you just go on:
final class UserService
{
public function __construct(private UserRepository $repository) { }
public function createNewUser(string $name): void
{
$user = new User(uniqid(), $name);
$this->repository->save($user);
}
}
Cool, everything is working.
Wait, let's not forget the tests
We want to verify when the UserService
creates an User
, if the UserRepository
is being called with correctly. We don't care now about the persistence, so we can just mock the repository and everything should work fine.
public function testFailing(): void
{
$repository = $this->createMock(UserRepository::class);
$userService = new UserService($repository);
$repository
->expects($this->once())
->method('save')
->with(new User('not so random id', 'user'));
$userService->createNewUser('user');
}
Until it doesn't. That test fails because User
with not so random id
doesn't match the argument called by UserRepository
which has a different id each time a new user is created.
Argument capture to the rescue!
In Java we have this same feature enabled by Mockito:
Argument Capture
allows us to create assertions on certain values of the arguments, instead of testing the equality for the whole object.
In our example we don't need to test the id
value, since it's randomic and it's being generated by PHP itself using the uniqid
function.
In the end this is how the test using argument capture
looks like:
public function testRepositoryShouldCreateUserWithCorrectName(): void
{
$repository = $this->createMock(UserRepository::class);
$userService = new UserService($repository);
$repository
->expects($this->once())
->method('save')
->will($this->returnCallback(function($user) {
self::assertEquals('user', $user->getName());
}));
$userService->createNewUser('user');
}
Thanks for reading this far, hope you enjoyed and good luck with your tests!