Uncle Bob once said
Testing Laravel
Before getting into the Laravel specific testing features, It's important to have a foundation with PHPUnit, Many PHP projects including Laravel use PHPUnit underneath, PHPUnit is the de-facto testing library, it's been around for over a decade, Out of the box it includes the test runner, base classes, powerful assertions and mocking.
Lets get familiar with some of the terminology, conventions and code.
PHPUnit Test runner
you will be using this often to run phpunit we can use the binary which composer installs under the vendor bin folder
vendor/bin/phpunit
this test runnner does few things automatically
PHPUnit Configuration file
first it loads any configuration from the phpunit.xml file
Laravel includes and presets values for running phpunit with your laravel project, your welcome to customize this. which takes to our first guess.
On most projects we need a database, phpunit.xml files ships with configuration for in memory sqlite database, which in most cases runs tests faster, creating one on the fly for our tests, so we uncomment those two lines.
Tests classes
Second based on the the unit will automatically run any of the test files under the test folder, By default this finds any PHP file where the file name ends with test. within each of these test files are test classes, these are any of the public methods which are either
- prefixed with test on their name
- have a test annotation. (inline or not inlined)
The default convention is using the test prefix, however you'll find many Laravel projects use the test annotation.
// this 2 tests (methods) are identical
public function test_true_is_true()
{
$this->assertTrue(true);
}
/** @test */
public function true_is_true()
{
$this->assertTrue(true);
}
The assertion methods:
Out of the box PHPUnit comes with nearly a hundred different assertions.
PHPUnit documentation
The assertion methods are declared static and can be invoked from any context using
PHPUnit\Framework\Assert::assertTrue()
, for instance, or using$this->assertTrue()
orself::assertTrue()
, for instance, in a class that extendsPHPUnit\Framework\TestCase
. You can even use global function wrappers such asassertTrue()
.A common question, especially from developers new to PHPUnit, is whether using
$this->assertTrue()
orself::assertTrue()
, for instance, is “the right way” to invoke an assertion. The short answer is: there is no right way. And there is no wrong way, either. It is a matter of personal preference.
/** @test */
public function array_has_given_key()
{
$array = ['color' => 'red' ];
// this two are identical
$this->assertArrayHasKey('color', $array);
self::assertArrayHasKey('color', $array);
}
Within each test case you perform assertions, These check whether code actually behaves as expected.
In some cases, the assertion is a failure. (expects a failure)
UNIT tests
Now that we have some terminology let's bring this all together with a quick example:
- We create a file with a suffix Test, lets say ExampleTest.php, and we save it within Tests/Unit folder. this will let PHPUnit understand its a test file.
Running our tests will give us an error Class not found exception.
- So we add a class named ExampleTest and we extend TestCase. PHPUnit Framework TestCase.
We would run into no assertions warnings, so we need to add a test with an assertion.
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class ExampleTest extends TestCase
{
/** @test */
public function true_is_true()
{
$this->assertTrue(true);
$this->assertNull(null);
}
}
This will give us a what developers call green, which means all tests did run and have made proper assertions, we would see how many tests and assertions. stats like how much time it took.
- Number of tests and assertions.
- The percentage of success.
Laravel TestClass
Laravel comes with a built in TestCase, it does extend the PHPUnit test class, however it does add more functionality, and when running a test it does actually runs an application, visits a route, hit a controller or a middleware.
This would return a response, Laravel wraps this response in a test response object, this contains assertion methods helpful to assert against the response.
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_example()
{
$response = $this->get('/');
$response->assertStatus(200);
}
We will go in deep with the jargon integration, feature or unit tests.
Test failure
/** @test */
public function true_is_true()
{
// we know this will fail
$this->assertTrue(false);
}
- failure indicated by a red F.
- an error message detailing for us why the message failed.
- and the line number for refrence
Expected versus Actual and Subject
Whatever the thing we are testing is a subjet, its common to always test something (a creation of a resource, a page loading, or a mail sent..etc )
When making an assertion, you often hear, $expected and $actual,
So the expected value is likely to be hardcoded, and the $actual value will be the result on calling some method on the subject.
Arrange Act Assert
Every test, will have 3 phases, think of it this way:
Given we have certain scenario (maybe data from the database, or that we are signed in, or trying to click a button..etc)
When we act, is the bhaviour we want (visit a route, or call a method, or post data..etc)
We make sure our expectations matches the actual.