Using Traits to Organise PHPUnit Tests
Tags: phpunit, testing, php
Published: 2020-04-15 15:02:32

---
title: Using Traits to Organise PHPUnit Tests
published: true
description: How I've been organising test classes
tags: phpunit, testing, php
---

I'd like to share a way I've been organising tests in PHPUnit. Maybe you've tried it before already, and maybe it's a terrible idea. What's worked for me might not work for you.

I'm going to assume you've written tests before and know some PHP. Let's jump in!

## The example setup

Let's start with testing a blogging platform like Dev. It has an API and a web interface and sends emails to an author's followers when a new article is posted. 

Here's a test where a user is authenticated with an API, publishes an article, but has no followers, so no emails are sent:

```php
public function test_no_emails_sent_for_author_with_no_followers(): void
{
    $this->givenApiAuthenticatedAsUser('1');
    $this->api->post('/articles', ['content' => '...', 'published' => true]);
    $this->emails->assertCountEmails(0);
}
```

There's a few things missing.

 * Where is `$this->givenApiAuthenticatedAsUser` defined?
 * How is `$this->api` setup?
 * What about `$this->emails`?

Let's look at `$this->api` first. It could be any API client, like Guzzle or a Symfony HttpClient. It's not important which one here, but it'd be defined something like this

```php
public function setUp(): void
{
    $this->api = new SomeClient('https://api.blog.example.com', ...);
}
```

Next, `$this->givenApiAuthenticatedAsUser`. For our purposes, this simulates an OAuth token and adds it as a header like this

```php
protected function givenApiAuthenticatedAsUser(string $userId): void
{
    $token = 'test token';
    $this->api->headers['Authorization'] = "Bearer $token";
}
```

Finally, `$this->emails`. Let's assume this is an assertion library you've found for Mailhog (though I'm not sure one actually exists).

```php
public function setUp(): void
{
    $this->emails = new SomeMailhogAssertionClient('http://mailhog.test');
    $this->emails->reset();
}
```

## Organisation with traits

I'm sure you can imagine several tests in this fictional setup, e.g. `ArticlesApiTest`, `UsersApiTest` and `UsersWebTest`. The last one might use BrowserKit or Panther but I'll skip the details here since it's the same as the API setup.

Since `ArticlesApiTest` needs the setup code for the API _and_ the setup code for emails but we don't want to duplicate it all the time, one solution might to put the `setUp` in `BaseTest` and extend from that.

This gets a bit tricky if we're already extending from Symfony's WebTestCase or KernelTestCase for example. It's one of the cases where [we'd prefer to use composition](https://en.wikipedia.org/wiki/Composition_over_inheritance).

Since we don't have any sort of dependency injection options in PHPUnit which would cover this, and some of our methods are accessed directly via `$this`, we'll do it with [Traits](https://www.php.net/manual/en/language.oop5.traits.php) and [PHPUnit annotations](https://phpunit.readthedocs.io/en/9.0/annotations.html).

Since we've seen all the parts individually already, a larger example will be better here:

```php
class ArticlesApiTest
{
    use ApiTestTrait;
    use EmailsTestTrait;

    public function test_no_emails_sent_for_author_with_no_followers(): void
    {
        $this->givenApiAuthenticatedAsUser('1');
        $this->api->post('/articles', ['content' => '...', 'published' => true]);
        $this->emails->assertCountEmails(0);
    }
}

trait ApiTestTrait
{
    /**
     * @before
     **/
    public function setUpApiBeforeTest(): void
    {
        $this->api = new SomeClient('https://api.blog.example.com', ...);
    }

    protected function givenApiAuthenticatedAsUser(string $userId): void
    {
        $token = 'test token';
        $this->api->headers['Authorization'] = "Bearer $token";
    }
}

trait EmailsTestTrait
{
    /**
     * @before
     **/
    public function setUpEmailsBeforeTest(): void
    {
        $this->emails = new SomeMailhogAssertionClient('http://mailhog.test');
        $this->emails->reset();
     }
}

```

By replacing `setUp` with uniquely named methods and the `@before` annotation, we're able to split the code into separate chunks that we can opt in to. 

Now resetting the emails, which makes a slow API call, only happens when we explicitly bring it in to our test. Our IDE autocompletion list is cleaner because it knows when we might or might not use certain features or assertions. 

We're also free to extend another class if we have to, e.g. `WebTestCase` (If any Symfony contributors are reading, I think it'd be great if we could `use WebTestCase` instead).

## Extra example Traits

Now that I've shown the concept, I want to show a few other examples and ideas for how you could use it.

**Mocking global state, like time?**

```php
trait MockedClockTestTrait
{
    protected function givenTimeIs(\DateTimeImmutable $time): void
    {
        // Populate a mocked time object, 
        // or use Clock Mocking from PHPUnit Bridge
    }
}
```

**Have a lot of assertions on JSON?**

```php
trait JsonAssertionsTrait
{
    public function assertJsonContainsKey(): void;
    public function assertJsonHasValueAtPath(string $jsonPath, $expectedValue): void;
}
```

**Reset the database in some tests and persist objects with JSON**

```php
trait DatabaseTestTrait
{
    /**
     * @var \Doctrine\Common\Persistence\ObjectManager
     */
    protected $objectManager;

    abstract protected static function getContainer(): ContainerInterface;

    /**
     * @before
     */
    public function resetDatabaseBeforeTest(): void
    {
        $registry = static::getContainer()->get('doctrine');
        $connection = $registry->getConnection();

        $this->objectManager = $registry->getManager();

        $connection->executeUpdate('DELETE FROM articles');
        $connection->executeUpdate('DELETE FROM users');
    }
}
```

What do you think? Have you used this method before? Any downsides? 

What would you put in a trait?