In one sentence

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.

Why it matters

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 big picture

The testing pyramid

Browser Feature tests Unit tests few · slow · realistic HTTP + DB many · fast · focused
Write lots of fast unit tests, a solid layer of feature tests, and only a few slow end-to-end browser tests.
TypeLives inChecksSpeed
Unittests/UnitOne class or function on its ownMilliseconds
Featuretests/FeatureA full request: route → controller → DB → responseFast
Browsere.g. tests/BrowserA real browser clicking through pages (Dusk or Pest browser plugin)Slow
Step by step

Run and write your first test

  1. 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…)
  2. 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 cores
            
    
                 PASS  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
            
  3. Read a Pest test

    A Pest test is a call to test() (or it()) 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);
                  }
              }
            
  4. 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');
              });
            

    RefreshDatabase resets the database for each test, so tests never see each other's data. You'll learn factories in Factories & Seeders.

  5. 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 environment

    phpunit.xml sets APP_ENV=testing and usually an in-memory SQLite database, so tests never touch your real data.

Common mistakes

Avoid this, do that

❌ Avoid

          test('posts work', function () {
              // creates, edits, deletes,
              // checks emails, logs out...
          });
        

One giant test hides which behaviour broke when it fails.

✅ Do

          test('an author can create a post', ...);
          test('guests cannot create posts', ...);
        

One behaviour per test, named like a sentence, makes failures self-explanatory.

💡 Pro tip: run tests constantly

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.

🏋️ Try it yourself
  1. Run php artisan test in a fresh app and read the output.
  2. Create a test with php artisan make:test AboutPageTest that checks /about returns 200.
  3. Make it fail deliberately (expect 404), read the message, then fix it.
  4. Rewrite the test with clear // Arrange, // Act, // Assert comments.
Recap

Key takeaways

Check yourself

Mini quiz