Argument capture in PHP tests
Tags: php, webdev, tutorial, testing
Published: 2021-10-16 09:37:16

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.

![Gif showing someone horrorified](https://media.giphy.com/media/1iUZa41YxKQtaJq0/giphy.gif)

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

![Gif of someone thinking this is going to be easy](https://media.giphy.com/media/kzs7srxbKNqjbI62U4/giphy.gif)

### User class
Couldn't be simpler, right?
```php
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.

```php
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:
```php
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
![Gif of someone rembering to test](https://media.giphy.com/media/3o6Mbbs879ozZ9Yic0/giphy.gif)

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.
```php
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](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#15):

> `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:
```php
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!

![Good luck!](https://media.giphy.com/media/j1Xyt3DHfJcmk/giphy.gif)