Laravel testing tips & tricks

Paul
Star Gazers
Published in
7 min readMar 8, 2021

Improve tests for your laravel application

Photo by Jack B on Unsplash

Introduction

PHPUnit is a unit testing framework for the PHP programming language. Laravel has support for PHPUnit included out of the box, and a phpunit.xml file is already set up for your application.

In your project your tests directory will contain 2 directories: Feature and Unit. Unit tests are tests that focus on a very small, isolated portion of your code. In fact, most unit tests probably focus on a single method. Tests within your "Unit" test directory do not boot your Laravel application and therefore are unable to access your application's database or other framework services.

Feature tests may test a larger portion of your code, including how several objects interact with each other or even a full HTTP request to a JSON endpoint. Generally, most of your tests should be feature tests. These types of tests provide the most confidence that your system as a whole is functioning as intended.

Set up

By default Laravel will use your database settings from your .env file. To get started quickly you should be able to override these settings in the phpunit.xml file by adding the following configurations:

<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>

This will run your tests in an ‘in memory’ sqlite database.

Photo by Joshua Sortino on Unsplash

Running tests

An ExampleTest.php file is provided in both the Feature and Unit test directories. After installing a new Laravel application, execute the command vendor/bin/phpunit or php artisan test to run your tests.

Normally tests will be run sequentially, however since January 2021, you can now execute tests in parallel, often vastly reducing the time it takes to run tests.

To do this, ensure you are running the latest version of Collision and Laravel 8:

composer update nunomaduro/collision laravel/framework

Then include the --parallel option when executing the test command.

php artisan test --parallel

Flags

Any flags that can be passed to the phpunit command can also be passed to the Artisan test command.

--filter

This will filter the tests to run. Tests can be filtered by method name or class.

--testsuite

This will filter the testsuites to run. To find out the testsuite options you can run: --list-suites.The default options are Unit and Feature.

--stop-on-failure

This will stop the test execution upon the first error or failure. There are many other flags, but we can’t go through them all in this article. To see the list of options run: --/vendor/bin/phpunit --help

Creating a new test

To create a new test case, use the make:test Artisan command:

php artisan make:test UserTest

By default, tests will be placed in the tests/Feature directory. To place a test within the tests/Unit directory you may use the --unit flag.

Auth

actingAs

Sets the currently logged in user for the application. If you want to act as a certain type of user pass a user object into this method.

$this->actingAs(User::factory()->create());
Photo by Jeswin Thomas on Unsplash

Assert

Asserts usually come towards the end of your test and they just mean ‘check this is what I expect it to be’.

assertDatabaseHas

Assert that the record which should have been created has indeed been created.

assertInstanceOf

Assert that the object is of a given type:

$this->assertInstanceOf(Collection::class, $user->posts);

assertRedirect

Assert whether the response is redirecting to a given URI. After a post is created there is a redirect in my controller: return redirect('/posts');

I can test the redirect is working using assertRedirect:

$this->post('posts', $attributes)->assertRedirect('/posts');

This is checking that, after my post has been created, that it will redirect to the posts endpoint.

assertStatus, assertJsonStructure, assertJsonFragment

Assert that the response has the status code ‘200’, assert that the json response has the given structure, and assert that the json response contains the given json fragment.

$this->json('GET', '/posts')->assertStatus(200)->assertJsonStructure(['data' => ['*' => ['id','title']]])->assertJsonFragment(['title' => 'Example post']);

assertSee

Assert that the given string or array of strings are contained within the response. For example:

$this->get('/posts')->assertSee($attributes['title']);

Here I assert that when I retrieve the posts from my posts endpoint they will have a title.

assertSessionHasErrors

Assert that the request validation is returning the correct errors. For example, say I wan’t to ensure all my posts have a title:

public function test_a_post_requires_a_title()
{
$this->post('/posts', [])->assertSessionHasErrors('title');
}

Here I am passing an empty array to the posts endpoint — simulating a request without a title property. Then I am asserting that I will see an error because I don't have a title for the post.

You can find a list of phpunit assertions here: https://phpunit.readthedocs.io/en/9.5/assertions.html

Using faker

Faker is a useful library to generate fake data quickly and easily https://github.com/fzaninotto/Faker. For example say you would like a real sounding name, address, company name you can simply say faker->name and it will pick one at random.

Now if we wat to pull this library into our test we can simply add a use statement at the top of our class, and simply call the relevant faker method:

<?phpnamespace Tests\\Feature;use Illuminate\\Foundation\\Testing\\WithFaker;
use Tests\\TestCase;
class PostsTest extends TestCase
{
use WithFaker;
public function a_user_can_create_a_post()
{
$attributes = [
'title' => $this->faker->sentence,
'description' => $this->faker->paragraph,
];
$this->post('posts', $attributes);$this->assertDatabaseHas('posts', $attributes);
}
}
Photo by Alessio Ferretti on Unsplash

Model Factories

When testing, you may need to insert a few records into your database before executing your test. Instead of manually specifying the value of each column when you create this test data, Laravel allows you to define a set of default attributes for each of your Eloquent models using model factories.

Creating a new model factory

To create a new factory, run the artisan make command:

php artisan make:factory PostFactory --model=Post

The new factory class will be placed in your database/factories directory.

The --model option may be used to indicate the name of the model created by the factory.

If you are using laravel 8, your factory will have a definition method. Older versions will have a define method. Either way, you need to populate the return method with the data to be generated when generating your models:

/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'title' => $this->faker->sentence,
'desription' => $this->faker->paragraph,
];
}

This will use the faker library to generate new data for title and description of each new Post model created with this factory.

Now anywhere I run $post = Post::factory()->make(); I will have generated a new instance of the model. Note that the make() method will not persist the record in the database. If you want to save to the database use create().

If I want to supply some of the data, and have the remaining fields populated with fake data I can do this:

$post = Post::factory()->make(['title' => 'My title']);

We saw that create saves a record to the database, make creates a model object but keeps it in memory but there is another useful method: raw. The raw method will save the model attrbutes to an array.

For example, my assertSessionHasErrors test could have been:

public function test_a_post_requires_a_title()
{
$attributes = Post::factory()->raw(['title' => '']);

$this->post('/posts', $attributes)->assertSessionHasErrors('title');
}

Rather than sending an empty array, I am now sending an empty title, but all the other fields will be supplied and valid. This way, no other validation issues will interfere with what we are trying to test.

Photo by Fredrik Öhlander on Unsplash

Disable exception handling

By default when an exception is thrown Laravel will catch it and handle it gracefully. For testing though we do not always want that to happen — sometimes we do actually want to see the exception.

To achieve this add $this->withoutExceptionHandling(); at the start of the test. This is useful, for example, if you are sending a request which is failing, but yet you wouldn't see the error until you test to see if a database record has been created. Disabling exception handling will throw the error when the request fails.

Tidy up

We want to run each test as isolated as possible and so it is important to clear things up after each test has executed.

To clear up any database records after each test we can use the trait RefreshDatabase. Simply add the statement use Refreshdatabase; within your class.

Conclusion

Testing Laravel applications is such a vast topic that we can only begin to scratch the surface in one article. I have tried to cover some of the more used, or under used testing methods. Hopefully this has been of some use to you — thanks for reading!

--

--

Paul
Star Gazers

I am a software developer and I like to write about interesting things I come across in my day to day.