Testing basics
An automated test is code that checks other code: it opens a page or calls a function and compares the result with the expected one. The test suite is run with one command after every change, and you see at once whether something broke. A CodeIgniter project already has PHPUnit — the program that runs tests — configured, and the tests themselves live in the tests folder.
The first test
Create the file tests/unit/BlogHelperTest.php by hand. It checks the reading_time() function from the lesson about helpers:
// tests/unit/BlogHelperTest.php
use CodeIgniter\Test\CIUnitTestCase;
class BlogHelperTest extends CIUnitTestCase
{
public function testShortTextTakesOneMinute(): void
{
helper('blog');
$this->assertSame('1 min read', reading_time('A short text'));
}
}The file and class name ends in Test, the method name starts with test. assertSame() compares the expected value with the actual one.
Running
vendor/bin/phpunitOn Windows the command is written as vendor\bin\phpunit. The terminal prints a dot for every passed test and a summary: OK (1 test, 1 assertion). If a check fails, the expected and actual values and the test line are shown.
Testing a page
The FeatureTestTrait trait lets you "open" a site address without a browser:
// tests/feature/HomeTest.php
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;
class HomeTest extends CIUnitTestCase
{
use FeatureTestTrait;
public function testLoginPageOpens(): void
{
$result = $this->get('login');
$result->assertStatus(200);
$result->assertSee('Log in');
}
}The get() method performs a request to a route and returns the response. The test checks that the login page opens with the 200 code and contains the words "Log in".
| Assertion | What it states |
|---|---|
assertStatus(200) | The status code of the response |
assertSee('text') | The text is on the page |
assertDontSee('text') | The text is not on the page |
assertRedirect() | The response is a redirect |
assertSessionHas('user_id') | The session has the value |
A test with a database
Tests work with a separate database — the tests connection from app/Config/Database.php — so that real data is not touched. The DatabaseTestTrait trait runs the migrations before the tests:
// tests/database/PostModelTest.php
use App\Models\PostModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
class PostModelTest extends CIUnitTestCase
{
use DatabaseTestTrait;
protected $migrate = true;
protected $refresh = true;
protected $namespace = 'App';
public function testInsertSavesPost(): void
{
model(PostModel::class)->insert(['title' => 'Test post', 'body' => 'Text']);
$this->seeInDatabase('posts', ['title' => 'Test post']);
}
}$namespace = 'App' says the migrations are taken from the app folder. seeInDatabase() checks that the table has a row with these values. By default the test database is SQLite in memory, which needs the sqlite3 PHP extension.
What to test
Start with what is most costly to break: logging in, saving a post, protected pages. A good test checks one statement and does not depend on other tests.
Common mistake
Database tests must not be pointed at the working connection: the $refresh property rolls the migrations back and drops the tables before every test. Do not replace the tests group in app/Config/Database.php with the details of the main database. If you need MySQL, create a separate empty database for tests, for example blog_test.