Skip to main content

Unit Testing in Laravel with PHP Unit

1. Setting Up Laravel for Testing

Laravel provides a TestCase class that extends PHPUnit’s base class, enabling you to write tests that integrate Laravel's features.

Step 1: Install Dependencies

Make sure you have the required dependencies:

composer install

Step 2: Run Tests

Run the default Laravel test suite:

php artisan test

OR

vendor/bin/phpunit

2. Creating a Test

Laravel stores tests in the tests directory, which includes Feature and Unit subdirectories:

  • Feature Tests: Test the functionality of controllers, middleware, and HTTP requests.
  • Unit Tests: Test specific classes or methods without Laravel's framework context.

Creating a Test Class

Use Artisan to generate a test:

php artisan make:test ExampleTest

For a unit test:

php artisan make:test ExampleTest --unit

3. Writing Your First Test

Here’s a simple example:

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class BasicTest extends TestCase
{
public function testBasicAssertion()
{
$this->assertTrue(true);
}
}

Run the test:

php artisan test

4. Testing Models

Example: Testing a User Model

Use Laravel's factories to set up test data.

namespace Tests\Unit;

use Tests\TestCase;
use App\Models\User;

class UserTest extends TestCase
{
public function testUserCreation()
{
$user = User::factory()->create([
'name' => 'Ali Shaibu-Salami',
'email' => 'ali@example.com',
]);

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

5. Testing HTTP Routes

Laravel provides the get, post, and other HTTP methods for testing routes.

Example: Testing a Homepage Route

namespace Tests\Feature;

use Tests\TestCase;

class HomePageTest extends TestCase
{
public function testHomePageReturnsSuccess()
{
$response = $this->get('/');

$response->assertStatus(200)
->assertSee('Welcome');
}
}

6. Testing Middleware

Middleware tests ensure your route protection or request modifications work as expected.

namespace Tests\Feature;

use Tests\TestCase;

class MiddlewareTest extends TestCase
{
public function testProtectedRoute()
{
$response = $this->get('/protected-route');

$response->assertStatus(403); // Assuming middleware blocks access
}
}

7. Mocking Dependencies

Mock Laravel services or external APIs using Laravel’s service container and PHPUnit's mocking capabilities.

Example: Mocking a Service

namespace Tests\Unit;

use Tests\TestCase;
use App\Services\ExternalService;
use Mockery;

class ExternalServiceTest extends TestCase
{
public function testMockingExternalService()
{
$mock = Mockery::mock(ExternalService::class);
$mock->shouldReceive('performOperation')
->andReturn('mocked result');

$this->app->instance(ExternalService::class, $mock);

$result = app(ExternalService::class)->performOperation();

$this->assertEquals('mocked result', $result);
}
}

8. Testing Database Interactions

Laravel provides assertions like assertDatabaseHas and assertDatabaseMissing to validate database interactions.

Example: Testing a Database Record

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\User;

class DatabaseTest extends TestCase
{
public function testDatabaseEntry()
{
User::factory()->create([
'email' => 'test@example.com',
]);

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

9. Testing Events, Jobs, and Queues

Laravel makes it simple to test these components using built-in helpers like Event::fake or Queue::fake.

Example: Testing an Event

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Support\Facades\Event;
use App\Events\OrderShipped;

class EventTest extends TestCase
{
public function testOrderShippedEvent()
{
Event::fake();

OrderShipped::dispatch();

Event::assertDispatched(OrderShipped::class);
}
}

10. Testing Artisan Commands

You can test Artisan commands using the artisan method.

Example: Testing a Command

namespace Tests\Feature;

use Tests\TestCase;

class ArtisanCommandTest extends TestCase
{
public function testArtisanCommand()
{
$this->artisan('your:command')
->expectsOutput('Command executed successfully')
->assertExitCode(0);
}
}

11. Advanced Testing Tools in Laravel

  • Factories

Generate test data:

User::factory()->count(5)->create();

  • Seeders

Seed the database during tests:

$this->seed(UserSeeder::class);

  • Custom Assertions

Create custom assertions to streamline testing patterns.

12. Running and Debugging Tests

Run all tests:

php artisan test

Run a specific test:

php artisan test --filter=HomePageTest

Generate code coverage:

vendor/bin/phpunit --coverage-html=coverage

tip

Best Practices

  1. Follow Arrange-Act-Assert (AAA): Clearly structure each test into three parts:

    • Arrange: Set up the test.
    • Act: Execute the function under test.
    • Assert: Verify the output.
  2. Test One Functionality per Test Case: Each test case should focus on a single aspect.

  3. Use Meaningful Names: Test method names should describe their purpose.

  4. Avoid Hardcoded Dependencies: Use mocks or stubs for dependencies.

  5. Run Tests Frequently: Integrate testing into your development workflow.