Testing is one of the topics that not only beginners but also some experienced developers ignore, one may think, why would I write code to test if my code is working? isn’t that a waste of time?. Well, this may be the case for a very small proof of concept project, but for bigger and more serious projects and as the project progresses, it will be harder to manage and add changes without risking breaking something mistakingly.
This is a guide for laravel developers who have never written tests for their applications
Here are some reasons why I always write tests for my projects (unless I am tinkering with a new idea):
Laravel comes pre-configured with the necessary tooling for testing so you don’t need to install any additional libraries. There is a couple of default test classes,tests/Feature/ExampleTest.php
and tests/Unit/ExampleTest.php
.
let’s assume we want to implement the user registration feature, here is what the workflow may look like:
We will be interacting with the database and we don’t want to use our development database to test our feature because we will be refreshing our database every time we run a test. So to use a lightweight in-memory SQLite database we need to uncomment two lines in phpunit.xml
file.
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
- <!-- <server name="DB_CONNECTION" value="sqlite"/> -->
- <!-- <server name="DB_DATABASE" value=":memory:"/> -->
+ <server name="DB_CONNECTION" value="sqlite"/>
+ <server name="DB_DATABASE" value=":memory:"/>
<server name="MAIL_MAILER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
<server name="TELESCOPE_ENABLED" value="false"/>
</php>
This tells laravel that we want to use an SQLite in-memory database when testing.
Notice that till this point we haven’t written any line of code, it is a fresh laravel installation.
We will start with the tests and let them guide us to the implementation. we will be using the file tests/Feature/ExampleTest.php
to keep things simple.
Before we start writing our tests, we need to add a trait to our test class to refresh the database before the test is run, so add this inside your test class:
+ use Illuminate\Foundation\Testing\RefreshDatabase;
class ExampleTest extends TestCase
{
+ use RefreshDatabase;
}
Inside the method test_example
, We want to post data to the route /register
and we expect that a new user is created with the data we provided, as simple as that.
This is our test and we will go through it line by line
/**
* A basic test example.
*
* @return void
*/
public function test_example()
{
$this->withoutExceptionHandling();
$data = [
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => '11111111',
];
$response = $this->post('/register', $data);
$response->assertStatus(200);
$this->assertDatabaseHas('users', [
'name' => $data['name'],
'email' => $data['email'],
]);
}
Because we want to see all the errors we get we will start by disabling exception handling ($this->withoutExceptionHandling();
).
Next, we will prepare our user data and we post this data to the url /register
, at this point we have done the work necessary to register a user, next we want to know if our request was successful and if the user is created in our database.
$response->assertStatus(200);
this will make sure that we get a 200 status code back from the server as a response status code.
Now to the most important check, we need to make sure that the user is actually present in our database, for this laravel provides a helpful method assertDatabaseHas
to check if a specific table has the value we expect to be present. In this case, we expect the database to have our user information and the hashed password.
This is it. now we are ready to start implementing the user registration feature.
To run the test use the command vendor/bin/phpunit
in your terminal. If we run the test we will get an error that says something like this:
Symfony\Component\HttpKernel\Exception\NotFoundHttpException: POST http://localhost:8000/register
At this point, we know that the test will fail because we don’t have a /register
route to handle the registration logic. So this is our next step.
In routes/web.php
file we will add a new route to handle the registration logic, for the sake of simplicity we will not worry about validating the user input (It is a best practice to always validate user input)
Route::post('/register', function () {
});
Now if we run the test again we will see the error:
Failed asserting that a row in the table [users] matches the attributes {
"name": "John Doe",
"email": "john@example.com"
}.
The table is empty..
Which is expected because we are not doing anything inside our route handler. So let's accept the user data and register the user.
use App\Models\User;
Route::post('/register', function () {
User::create([
'name' => request('name'),
'email' => request('email'),
'password' => bcrypt(request('password')),
]);
});
Now if we run the test it will pass and we will be confident that our registration endpoint is functioning as expected!
This is a very simplified test but hopefully, with this knowledge, you can start testing your laravel apps.
To learn more about testing laravel apps, you can consult The laravel docs