DEV Community

Peter Fox
Peter Fox

Posted on • Originally published at Medium on

4 1

Laravel Tips: Making your own trait ‘hooks’ for tests

Photo by Cameron Kirby on Unsplash

When writing feature tests there’s often a lot of boilerplate code that you might have to add to your tests. The default testing framework with Laravel is PHPUnit, a reliable if not somewhat difficult testing tool at time. It’s heavily based on JUnit, a testing cool for Java libraries. Normally if we want to add something to our tests setup we have to implement a Setup method which can be a bit tedious if we’d using the same one over lots of tests. Of course, we could also just add things to the setup method of our TestCase class but then that’s going to run for every test and sometimes we just need for particular tests. Luckily Laravel already has a mechanism baked into its own TestCase that we can modify to switch on and off with our traits.

An Example Trait

So first we need a trait. For the example I’m going to use something simple that pretty much everyone will have to do if making a feature tests, generate users for authentication.

So often a bit of code for a test this might look like this:

$user = factory(User::class)->create();

$this->actingAs($user)
    ->get('/home')
    ->assertOk();
Enter fullscreen mode Exit fullscreen mode

This can be a bit redundant as our tests get more complicated and we’re left creating a user for authentication for each test. Instead we’re going to put this into a trait that will create the user for each test at set up.

<?php
namespace Tests\Support;
use App\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Tests\TestCase;
trait Authentication
{
/** @var User $user **/
protected $user;
/**
* @before
*/
public function setupUser()
{
$this->afterApplicationCreated(function () {
$this->user = factory(User::class)->create();
});
}
public function authenticated(Authenticatable $user = null)
{
return $this->actingAs($user ?? $this->user);
}
}

What this will mean is we no longer have to generate our User model or even specify the user when we want to authenticate the request to the API, simply bringing in the trait will give us a user property in our test case.

We still have to change our TestCase to make this happen though. We’ll do this by adding a setUpTraits method to the TestCase class found in tests/TestCase.php which is made with every fresh Laravel application.

<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Tests\Support\Authentication;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
/**
* Boot the testing helper traits.
*
* @return array
*/
protected function setUpTraits()
{
$uses = parent::setUpTraits();
if (isset($uses[Authentication::class])) {
$this->setUpUser();
}
}
}
view raw TestCase.php hosted with ❤ by GitHub

The setUpTraits method is actually a part of the parent TestCast. This is how Laravel normally knows how to do things like refresh databases etc. It works by checking what traits are applied to a test. In this case the parent class method is doing this for us and just returns those traits to us as an array. We can then check if there is a key for the Trait we’re using. If there is we want it to set up the user using the setUpUser method found in our Authentication trait.

Now when we produce a test we only need use the trait Authentication and it’ll create a user. For example we can create the test below to open the home page for a user and then see if our user’s name is printed on the page.

<?php
namespace Tests\Feature;
use Tests\Support\Authentication;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
use RefreshDatabase, Authentication;
/**
* A basic test example.
*
* @return void
*/
public function testExample()
{
$this->authenticated()
->get('/home')
->assertOk()
->assertSeeText($this->user->name);
}
}
view raw ExampleTest.php hosted with ❤ by GitHub

It’s a small change but it’s one I think that really lightens the load when you’re making lots of Feature tests for your application. Just being able to do this opens up a lot of possibilities as well so your can keep your testing code in serviceable state.

If need to, you can always evolve this system also. In the following code I’ve made changes to the trait so you can customise the factory used on a per test case basis simply by checking for the existence of additional methods on the test case.

<?php
namespace Tests\Support;
use App\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Tests\TestCase;
trait Authentication
{
/** @var User $user **/
protected $user;
public function setupUser()
{
$attributes = [];
if (method_exists($this, 'getUserFactoryAttributes')) {
$attributes = $this->getUserFactoryAttributes();
}
$this->user = factory(User::class)->create($attributes);
}
public function authenticated(Authenticatable $user = null)
{
return $this->actingAs($user ?? $this->user);
}
}

Now all you would need to do is add a getUserFactoryAttributes method to a TestCase like so:

public function getUserFactoryAttributes()
{
    return [
        'email' => 'testaccount@mydomain.com',
    ];
}
Enter fullscreen mode Exit fullscreen mode

And there we have it, test setups we can swap out via Traits with mechanisms for customisation per test case. If you want to check out the code for this you can find it on Github.

I’m Peter Fox, a software developer in the UK who works with Laravel among other things. If you want to know more about me you can at https://www.peterfox.me and feel free to follow me @SlyFireFox on twitter for more Laravel tips and tutorials.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️