DEV Community

Cover image for A Comprehensive Guide to Testing in Laravel with PHPUnit
Chabba Saad
Chabba Saad

Posted on

A Comprehensive Guide to Testing in Laravel with PHPUnit

Introduction to Testing in Laravel

Testing is a critical aspect of software development that ensures your application works as intended. Laravel provides a robust testing suite out of the box with PHPUnit, a popular testing framework for PHP. This guide will walk you through setting up and running tests in Laravel, explaining the differences between unit and feature tests, and providing examples for various testing scenarios.

Folder Structure: Unit vs. Feature Tests

In Laravel, tests are typically organized into two main directories: Unit and Feature.

Unit Tests: These tests are designed to test small, isolated parts of your application, such as individual methods or classes. They are usually located in the tests/Unit directory. Each test function should start with the word test.

Example:

public function testExampleFunction() {
    $this->assertTrue(true);
}
Enter fullscreen mode Exit fullscreen mode

Feature Tests: These tests handle more complex interactions and typically test several components working together. They are located in the tests/Feature directory. Feature tests often involve making HTTP requests and checking responses.

Running Tests in Laravel

To run all tests in your Laravel application, use the following command:

./vendor/bin/phpunit
Enter fullscreen mode Exit fullscreen mode

Configuring the Testing Environment

Before running tests, it's important to configure your testing environment. Modify your phpunit.xml file to set environment variables for testing. For example, to use an SQLite in-memory database for faster tests:

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="APP_MAINTENANCE_DRIVER" value="file"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_STORE" value="array"/>
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
    <env name="MAIL_MAILER" value="array"/>
    <env name="PULSE_ENABLED" value="false"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="TELESCOPE_ENABLED" value="false"/>
</php>
Enter fullscreen mode Exit fullscreen mode

After enabling SQLite as your testing environment, clear the configuration cache:

php artisan config:clear

Enter fullscreen mode Exit fullscreen mode

*Example: Testing if Profile Route Exists and Works Correctly
*

Create a test for the profile route:

php artisan make:test ProfileTest
Enter fullscreen mode Exit fullscreen mode

Add a test method to check if the profile page displays specific text:

public function testProfilePage(){
    $response = $this->get('/profile');
    $response->assertSeeText('Your Profile');
}
Enter fullscreen mode Exit fullscreen mode

Testing Database Interactions

Setting Up a Testing Database

Before testing database interactions, create a testing database configuration in config/database.php:

'mysqltesting' => [
    'driver' => 'mysql',
    'url' => env('DB_URL'),
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => 'laravel_testing',
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],
Enter fullscreen mode Exit fullscreen mode

Running Database Tests

Run the PHPUnit command to ensure the test database is created and working:

./vendor/bin/phpunit
Enter fullscreen mode Exit fullscreen mode

database_check_creation

Example: Testing User Registration

Create a test for user registration:

php artisan make:test UserTest

Enter fullscreen mode Exit fullscreen mode

Add a test method to verify a user can be created and saved to the database:

public function test_registration(): void
{
    $user = new User();
    $user->name = 'Test User';
    $user->email = 'email@example.com';
    $user->password = bcrypt('password');

    $user->save();

    $this->assertDatabaseHas('users', ['email' => 'email@example.com']);
}
Enter fullscreen mode Exit fullscreen mode

Testing Store Action

Create a test for the store action in the PostTest class:

public function testPostStoreValid()
{
    $data = [
        'title'=> 'Test Post',
        'slug' => 'test-post',
        'content' => 'Content of the post',
        'active' => true,
    ];

    $this->post('/posts', $data)
         ->assertStatus(302)
         ->assertSessionHas('status');

    $this->assertEquals(session('status'), 'Post was created!');
}
Enter fullscreen mode Exit fullscreen mode

Testing for Failure

Add a test method to check for validation errors:

public function testPostFail()
{
    $data = [
        'title'=> '',
        'content' => '',
    ];

    $this->post('/posts', $data)
         ->assertStatus(302)
         ->assertSessionHas('errors');

    $messages = session('errors')->getMessages();

    $this->assertEquals($messages['title'][0], 'The title must be at least 4 characters.');
    $this->assertEquals($messages['title'][1], 'The title field is required.');
    $this->assertEquals($messages['content'][0], 'The content field is required.');
}
Enter fullscreen mode Exit fullscreen mode

*Testing Update Action
*

Add a test method for updating a post:

public function testPostUpdate()
{
    $post = new Post();

    $post->title = "Initial Title";
    $post->slug = Str::slug($post->title, '-');
    $post->content = "Initial content";
    $post->active = true;

    $post->save();

    $this->assertDatabaseHas('posts', $post->toArray());

    $data = [
        'title' => 'Updated Title',
        'slug' => 'updated-title',
        'content' => 'Updated content',
        'active' => false,
    ];

    $this->put("/posts/{$post->id}", $data)
         ->assertStatus(302)
         ->assertSessionHas('status');

    $this->assertDatabaseHas('posts', ['title' => $data['title']]);
    $this->assertDatabaseMissing('posts', ['title' => $post->title]);
}
Enter fullscreen mode Exit fullscreen mode

Testing Delete Action

Add a test method for deleting a post:

public function testPostDelete()
{
    $post = new Post();

    $post->title = "Title to delete";
    $post->slug = Str::slug($post->title, '-');
    $post->content = "Content to delete";
    $post->active = true;

    $post->save();

    $this->assertDatabaseHas('posts', $post->toArray());

    $this->delete("/posts/{$post->id}")
         ->assertStatus(302)
         ->assertSessionHas('status');

    $this->assertDatabaseMissing('posts', $post->toArray());
}
Enter fullscreen mode Exit fullscreen mode

**

Running Specific Tests with PHPUnit

**

To run a specific test method or class, use the --filter option with PHPUnit. Here are some examples:

Run a Specific Test Method

./vendor/bin/phpunit --filter PostTest::testPostDelete
Enter fullscreen mode Exit fullscreen mode

Run All Tests in a Specific Class

./vendor/bin/phpunit --filter PostTest
Enter fullscreen mode Exit fullscreen mode

Run Tests in a Specific File

./vendor/bin/phpunit tests/Feature/PostTest.php
Enter fullscreen mode Exit fullscreen mode

Verbose Output

For more detailed output, add the -v option:

./vendor/bin/phpunit --filter PostTest::testPostDelete -v
Enter fullscreen mode Exit fullscreen mode

Top comments (0)