By default Laravel separates tests by placing them in different test suites. These test suites are called:
In the "Unit" suite we will place and find tests that test a very specific part of the code in isolation. Most of the time this will be 1 class or maybe even just 1 function/method of a class. While in the "Feature" suite we will place and find tests that test a larger part of the code. Depending on the feature that is tested, this can include the actual HTTP request to the server, making for "End-to-End" tests.
To explain what unit tests are and how to write would go beyond the scope of this article. Instead we will discuss some of the basic tools that Laravel provides to write unit tests.
The first and most important tool Laravel provides us for testing is PHPUnit. PHPUnit allows us to write the actual tests and provides us some basic "assertion" methods. These assertions are very important as they make our tests fail when they should. Some examples are:
Of course there are many more, you can find the list of the most recent PHPUnit version here: https://phpunit.de/manual/current/en/appendixes.assertions.html
Another thing PHPUnit in combination with Laravel allows us to do is to specify a different configuration. That way it is possible to use a different database for testing as for development or use a different mail server, etc... (This can be done be specifying these variables in phpunit.xml or .env.testing for example).
Because Unit tests should test small portions of the code in isolation it is often necessary to mock the dependencies of a class or method. PHPUnit provides some features to mock functions or objects, but to make things easier Laravel provides us with Mockery and some built-in ways of mocking basic Laravel functions (like helpers and facades for example).
In web applications features are commonly defined by their API end point. For example a feature like "add member" would allow us to add a new "member" to a system that manages "member information" of team members. This feature should have an API end point, maybe the uri or route would look something like: /members/add/
.
Depending on the type of API the behavior of this API might be to try and save the record to the database using the Eloquent ORM and redirect to a page displaying a "success" message or redirect to a page displaying an "error" message when saving the information in the database fails.
When we are building an API for a "single page application" it is common to respond by returning a JSON encoded string containing either a "success" or "error" message, maybe even using a HTTP status code.
Laravel has some nice features to make it easy for us to test these end points.
Before we have anything to test (the response) we first have to make a request. Using Laravel we can do that in a variety of ways depending on the type of request we have to make:
public function testCanAddMember()
{
$response = $this->post(
'/members/add/',
['name' => 'Youri', 'age' => 32]
);
}
The above example shows how to perform a "POST" request, but there are also methods for "GET", "PUT" and "PATCH".
As you can see we assign the result of the call to a variable called $response
.
In addition to the "assertions" that PHPUnit provides us (which are available through $this->assert*
) we also have "assertions" available on the response object:
$response->assertStatus($code);
Assert that the response has a given code.
$response->assertRedirect($uri);
Assert that the response is a redirect to a given URI.
$response->assertHeader($headerName, $value = null);
Assert that the given header is present on the response.
$response->assertCookie($cookieName, $value = null);
Assert that the response contains the given cookie.
$response->assertPlainCookie($cookieName, $value = null);
Assert that the response contains the given cookie (unencrypted).
$response->assertSessionHas($key, $value = null);
Assert that the session contains the given piece of data.
$response->assertSessionHasErrors(array $keys);
Assert that the session contains an error for the given field.
$response->assertSessionMissing($key);
Assert that the session does not contain the given key.
$response->assertJson(array $data);
Assert that the response contains the given JSON data.
$response->assertJsonFragment(array $data);
Assert that the response contains the given JSON fragment.
$response->assertJsonMissing(array $data);
Assert that the response does not contain the given JSON fragment.
$response->assertExactJson(array $data);
Assert that the response contains an exact match of the given JSON data.
$response->assertJsonStructure(array $structure);
Assert that the response has a given JSON structure.
$response->assertViewIs($value);
Assert that the given view was returned by the route.
$response->assertViewHas($key, $value = null);
Assert that the response view was given a piece of data.
Which of the above "assertions" to use for our test depends heavily on what type of API we are testing.
For example when our API accepts our data and then provides us with a status code and a View:
public function testCanAddMember()
{
$response = $this->post(
'/members/add/',
['name' => 'Youri', 'age' => 32]
);
$response->assertStatusCode(200);
$response->assertViewIs('member.addSuccessful');
$response->assertViewHas('memberId', 1);
}
With these assertions the test will confirm that in this case the API responds with status 200
and returns the view member.addSuccessful
and the new id of the member is passed to the view using key memberId
.
When testing a JSON API things will look a little different:
public function testCanAddMember()
{
$response = $this->postJson(
'/members/add/',
['name' => 'Youri, 'age' => 32]
);
$response->assertStatusCode(200);
$response->assertJson(['status' => 200, 'message' => 'OK']);
}
Testing the response of the API end points is very important. Basically it confirms that they are working as they were intended to. Especially in the case of a single page application this is extremely important as the Javascript application relies on the responses of the API end points to keep track of the state of the application. If anything were to change in one of the API end points' behavior, there is a very big chance that the front end application will no longer be able to work correctly.
Next to the API responding as we expect it to, it is also very nice to be able to confirm that when we want to add a new record to the database, that not only do we get the correct response, but also that the record is actually added to the database.
We can do that with a assertion made available to us by the Laravel framework:
public function testCanAddMember()
{
$response = $this->postJson(
'/members/add/',
['name' => 'Youri, 'age' => 32]
);
$response->assertStatusCode(200);
$response->assertJson(['status' => 200, 'message' => 'OK']);
$this->assertDatabaseHas(
'members',
['id' => 1, 'name' => 'Youri', 'age' => 32]
);
}
An assertion that does exactly the opposite is also available: assertDatabaseMissing
.
Laravel provides many helpful tools to us, that make it much easier to test certain parts of our application. More information on this subject can be found here: https://laravel.com/docs/5.5/testing.