An automated test is a small piece of code that runs your app and checks it behaves the way you expect, so a machine can re-check everything in seconds.
✈️Pilots run a pre-flight checklist every single time, even after thousands of flights. Tests are your app's checklist: fast, boring and repeatable, which is exactly why they catch problems people miss.
Clicking around doesn't scale
Change code fearlessly
Refactor a controller, run the tests, and know within seconds whether you broke anything.
Bugs stay fixed
Write a test for each bug you fix. If it ever comes back, the test fails before your users notice.
Living documentation
A test named "guests cannot delete posts" explains your rules better than a comment.
The testing pyramid
| Type | Lives in | Checks | Speed |
|---|---|---|---|
| Unit | tests/Unit | One class or function on its own | Milliseconds |
| Feature | tests/Feature | A full request: route → controller → DB → response | Fast |
| Browser | e.g. tests/Browser | A real browser clicking through pages (Dusk or Pest browser plugin) | Slow |
Run and write your first test
-
Meet the test folder
Every new Laravel app already contains tests. When you run
laravel new, you choose a testing framework: Pest (the default) or PHPUnit. Pest is built on top of PHPUnit with a shorter syntax.tests/ ├── Feature/ │ └── ExampleTest.php ← requests the home page ├── Unit/ │ └── ExampleTest.php ← expects true to be true ├── Pest.php ← shared Pest setup └── TestCase.php phpunit.xml ← test env settings (DB, cache…)
-
Run the suite
One Artisan command runs every test and prints a friendly report.
php artisan test php artisan test --filter=homepage # only matching tests php artisan test --parallel # use all CPU coresPASS Tests\Unit\ExampleTest ✓ that true is true 0.01s PASS Tests\Feature\ExampleTest ✓ the application returns a successful response 0.12s Tests: 2 passed (2 assertions) Duration: 0.31s -
Read a Pest test
A Pest test is a call to
test()(orit()) with a description and a closure. Inside, assertions check results. If any assertion is false, the test fails.<?php test('the application returns a successful response', function () { $response = $this->get('/'); $response->assertStatus(200); });The same check with PHPUnit is a class whose method names start with
test:class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response(): void { $this->get('/')->assertStatus(200); } } -
Structure every test as Arrange, Act, Assert
Good tests have three parts. Arrange: set up the data. Act: do the one thing you're testing. Assert: check the outcome.
use App\Models\Post; use Illuminate\Foundation\Testing\RefreshDatabase; uses(RefreshDatabase::class); test('a published post can be viewed', function () { // Arrange $post = Post::factory()->create(['title' => 'Hello tests']); // Act $response = $this->get("/posts/{$post->id}"); // Assert $response->assertOk()->assertSee('Hello tests'); });RefreshDatabaseresets the database for each test, so tests never see each other's data. You'll learn factories in Factories & Seeders. -
Watch a test fail on purpose
A test you've never seen fail might not be testing anything. Break the expectation and confirm the failure message makes sense.
FAIL Tests\Feature\PostPageTest ⨯ a published post can be viewed Failed asserting that 'Hello tests' is contained in the response.ℹ️ Tests use their own environmentphpunit.xmlsetsAPP_ENV=testingand usually an in-memory SQLite database, so tests never touch your real data.
Avoid this, do that
test('posts work', function () {
// creates, edits, deletes,
// checks emails, logs out...
});
One giant test hides which behaviour broke when it fails.
test('an author can create a post', ...);
test('guests cannot create posts', ...);
One behaviour per test, named like a sentence, makes failures self-explanatory.
Run php artisan test before every commit. Many teams also run it automatically on every push with CI (for example, GitHub Actions), so broken code can't be merged.
- Run
php artisan testin a fresh app and read the output. - Create a test with
php artisan make:test AboutPageTestthat checks/aboutreturns 200. - Make it fail deliberately (expect 404), read the message, then fix it.
- Rewrite the test with clear
// Arrange,// Act,// Assertcomments.
Key takeaways
- Automated tests let you change code confidently and keep bugs from returning.
- The pyramid: many unit tests, a solid layer of feature tests, few browser tests.
- New Laravel apps default to Pest; PHPUnit class-based tests work too.
php artisan testruns everything;--filternarrows it down.- Structure tests as Arrange → Act → Assert, one behaviour per test.