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
Best Practices
-
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.
-
Test One Functionality per Test Case: Each test case should focus on a single aspect.
-
Use Meaningful Names: Test method names should describe their purpose.
-
Avoid Hardcoded Dependencies: Use mocks or stubs for dependencies.
-
Run Tests Frequently: Integrate testing into your development workflow.