Appearance
๐งช Testing Strategies โ
Comprehensive testing standards for Laravel applications. This section covers PHPUnit setup, testing strategies, mocking techniques, and quality assurance practices.
โ๏ธ PHPUnit Setup โ
Configure PHPUnit for optimal testing experience.
โ Good Example โ
xml
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Integration">
<directory suffix="Test.php">./tests/Integration</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
<exclude>
<directory>./app/Console</directory>
<file>./app/Http/Kernel.php</file>
</exclude>
</coverage>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="TELESCOPE_ENABLED" value="false"/>
</php>
</phpunit>โ Bad Example โ
xml
<!-- phpunit.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
<!-- No test suites defined -->
<!-- No coverage configuration -->
<!-- No environment variables -->
<!-- Uses production database -->
</phpunit>๐งช Unit Testing โ
Test individual classes and methods in isolation.
โ Good Example โ
php
<?php
namespace Tests\Unit;
use App\Models\User;
use App\Services\UserService;
use App\Repositories\UserRepositoryInterface;
use Tests\TestCase;
use Mockery;
class UserServiceTest extends TestCase
{
private UserService $userService;
private UserRepositoryInterface $userRepository;
protected function setUp(): void
{
parent::setUp();
$this->userRepository = Mockery::mock(UserRepositoryInterface::class);
$this->userService = new UserService($this->userRepository);
}
public function test_create_user_successfully(): void
{
// Arrange
$userData = [
'name' => 'John Doe',
'email' => '[email protected]',
'password' => 'password123',
];
$expectedUser = new User($userData);
$this->userRepository
->shouldReceive('create')
->once()
->with(Mockery::on(function ($data) {
return isset($data['name']) &&
isset($data['email']) &&
isset($data['password']) &&
$data['password'] !== 'password123'; // Should be hashed
}))
->andReturn($expectedUser);
// Act
$result = $this->userService->createUser($userData);
// Assert
$this->assertInstanceOf(User::class, $result);
$this->assertEquals('John Doe', $result->name);
$this->assertEquals('[email protected]', $result->email);
}
public function test_create_user_with_invalid_data_throws_exception(): void
{
// Arrange
$invalidData = [
'name' => '',
'email' => 'invalid-email',
];
// Act & Assert
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Name and email are required');
$this->userService->createUser($invalidData);
}
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
}โ Bad Example โ
php
<?php
namespace Tests\Unit;
use App\Services\UserService;
use Tests\TestCase;
class UserServiceTest extends TestCase
{
public function test_create_user()
{
// No setup
// No mocking
// Uses real database
// No proper assertions
$userService = new UserService();
$user = $userService->createUser([
'name' => 'John',
'email' => '[email protected]',
]);
$this->assertTrue(true); // Meaningless assertion
}
}๐ง Feature Testing โ
Test complete user workflows and API endpoints.
โ Good Example โ
php
<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class UserManagementTest extends TestCase
{
use RefreshDatabase, WithFaker;
public function test_user_can_create_account(): void
{
// Arrange
$userData = [
'name' => $this->faker->name(),
'email' => $this->faker->safeEmail(),
'password' => 'SecurePassword123!',
'password_confirmation' => 'SecurePassword123!',
];
// Act
$response = $this->postJson('/api/users', $userData);
// Assert
$response->assertStatus(201)
->assertJsonStructure([
'data' => [
'id',
'name',
'email',
'created_at',
],
])
->assertJsonMissing(['password']);
$this->assertDatabaseHas('users', [
'name' => $userData['name'],
'email' => $userData['email'],
]);
}
public function test_user_cannot_create_account_with_invalid_data(): void
{
// Arrange
$invalidData = [
'name' => '',
'email' => 'invalid-email',
'password' => '123',
];
// Act
$response = $this->postJson('/api/users', $invalidData);
// Assert
$response->assertStatus(422)
->assertJsonValidationErrors(['name', 'email', 'password']);
}
public function test_authenticated_user_can_view_profile(): void
{
// Arrange
$user = User::factory()->create();
// Act
$response = $this->actingAs($user)
->getJson('/api/profile');
// Assert
$response->assertStatus(200)
->assertJson([
'data' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
],
]);
}
public function test_unauthenticated_user_cannot_view_profile(): void
{
// Act
$response = $this->getJson('/api/profile');
// Assert
$response->assertStatus(401);
}
}โ Bad Example โ
php
<?php
namespace Tests\Feature;
use Tests\TestCase;
class UserManagementTest extends TestCase
{
public function test_user_creation()
{
// No database refresh
// No proper data setup
// No authentication testing
// No proper assertions
$response = $this->post('/api/users', [
'name' => 'John',
'email' => '[email protected]',
]);
$this->assertEquals(200, $response->status());
}
}๐ญ Mocking and Stubbing โ
โ Good Example โ
php
<?php
namespace Tests\Unit;
use App\Services\EmailService;
use App\Services\UserService;
use App\Repositories\UserRepositoryInterface;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
use Mockery;
class UserServiceWithEmailTest extends TestCase
{
public function test_create_user_sends_welcome_email(): void
{
// Arrange
Mail::fake();
$userRepository = Mockery::mock(UserRepositoryInterface::class);
$emailService = Mockery::mock(EmailService::class);
$userData = [
'name' => 'John Doe',
'email' => '[email protected]',
'password' => 'password123',
];
$user = new User($userData);
$userRepository
->shouldReceive('create')
->once()
->andReturn($user);
$emailService
->shouldReceive('sendWelcomeEmail')
->once()
->with($user);
// Act
$userService = new UserService($userRepository, $emailService);
$result = $userService->createUser($userData);
// Assert
$this->assertInstanceOf(User::class, $result);
Mail::assertSent(WelcomeEmail::class);
}
}โ Bad Example โ
php
<?php
// No mocking - tests depend on external services
class UserServiceTest extends TestCase
{
public function test_create_user()
{
// Uses real email service
// Sends real emails during testing
// Tests are slow and unreliable
$userService = new UserService();
$user = $userService->createUser([
'name' => 'John',
'email' => '[email protected]',
]);
// How do we verify email was sent?
$this->assertTrue(true);
}
}๐ญ Model Factories โ
โ Good Example โ
php
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class UserFactory extends Factory
{
protected $model = User::class;
public function definition(): array
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
'role' => 'user',
'is_active' => true,
];
}
public function admin(): static
{
return $this->state(fn (array $attributes) => [
'role' => 'admin',
]);
}
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}โ Bad Example โ
php
<?php
// Hard-coded factory data
class UserFactory extends Factory
{
public function definition(): array
{
return [
'name' => 'John Doe', // Always the same
'email' => '[email protected]', // Always the same
'password' => 'password', // Plain text password
'role' => 'user', // No variations
];
}
}๐ Test Coverage โ
โ Good Example โ
bash
# Run tests with coverage
php artisan test --coverage
# Generate HTML coverage report
php artisan test --coverage-html coverage/
# Minimum coverage threshold
php artisan test --coverage --min=80โ Bad Example โ
bash
# No coverage reporting
php artisan test
# No minimum coverage requirements
# No coverage analysis๐ฏ Testing Best Practices โ
โ Do's โ
- Write Tests First - Follow TDD/BDD practices
- Use Descriptive Names - Test names should explain what they test
- Arrange-Act-Assert - Structure tests clearly
- Mock External Dependencies - Keep tests isolated
- Use Factories - Generate test data consistently
- Test Edge Cases - Cover error conditions
- Maintain High Coverage - Aim for 80%+ coverage
- Use Database Transactions - Keep tests fast
- Test Both Success and Failure - Cover all scenarios
- Keep Tests Independent - Each test should be standalone
โ Don'ts โ
- Don't test implementation details - Test behavior, not code
- Don't skip error cases - Test failure scenarios
- Don't use real external services - Mock everything
- Don't write slow tests - Keep tests fast
- Don't ignore test maintenance - Update tests with code changes
- Don't test private methods - Test public interfaces
- Don't skip integration tests - Test complete workflows
- Don't ignore test data cleanup - Use RefreshDatabase
๐ง Testing Tools โ
Static Analysis โ
bash
# PHPStan
./vendor/bin/phpstan analyse
# Larastan (Laravel-specific)
./vendor/bin/phpstan analyse
# PHP CS Fixer
./vendor/bin/php-cs-fixer fix
# Laravel Pint
./vendor/bin/pintCode Quality โ
bash
# PHPUnit with coverage
./vendor/bin/phpunit --coverage-text
# Infection (mutation testing)
./vendor/bin/infection
# PHP Mess Detector
./vendor/bin/phpmd app text cleancode,codesize,design,naming,unusedcode๐งช Test Quality: Good tests are fast, reliable, maintainable, and provide confidence in your code. Invest time in writing quality tests.