Testing DTOs
Complete guide to testing SimpleDTO classes.
Introduction
Section titled “Introduction”Testing DTOs ensures data integrity and validation:
- Unit Tests - Test DTO creation and methods
- Validation Tests - Test validation rules
- Integration Tests - Test with frameworks
- Feature Tests - Test in controllers
- Performance Tests - Test performance
Unit Tests
Section titled “Unit Tests”Basic DTO Creation
Section titled “Basic DTO Creation”use PHPUnit\Framework\TestCase;
class UserDTOTest extends TestCase{ public function test_creates_dto_from_array(): void { $dto = UserDTO::fromArray([ 'id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', ]);
$this->assertEquals(1, $dto->id); $this->assertEquals('John Doe', $dto->name); $this->assertEquals('john@example.com', $dto->email); }
public function test_serializes_to_array(): void { $dto = new UserDTO( id: 1, name: 'John Doe', email: 'john@example.com', );
$array = $dto->toArray();
$this->assertArrayHasKey('id', $array); $this->assertArrayHasKey('name', $array); $this->assertArrayHasKey('email', $array); $this->assertEquals(1, $array['id']); }
public function test_serializes_to_json(): void { $dto = new UserDTO( id: 1, name: 'John Doe', email: 'john@example.com', );
$json = $dto->toJson(); $decoded = json_decode($json, true);
$this->assertIsString($json); $this->assertEquals(1, $decoded['id']); $this->assertEquals('John Doe', $decoded['name']); }}Validation Tests
Section titled “Validation Tests”Test Required Fields
Section titled “Test Required Fields”class CreateUserDTOTest extends TestCase{ public function test_validates_required_fields(): void { $this->expectException(ValidationException::class);
CreateUserDTO::validateAndCreate([ 'name' => 'John Doe', // email is missing ]); }
public function test_validates_email_format(): void { $this->expectException(ValidationException::class);
CreateUserDTO::validateAndCreate([ 'name' => 'John Doe', 'email' => 'invalid-email', 'password' => 'Password123', ]); }
public function test_validates_password_strength(): void { $this->expectException(ValidationException::class);
CreateUserDTO::validateAndCreate([ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => 'weak', // Too short ]); }
public function test_creates_valid_dto(): void { $dto = CreateUserDTO::validateAndCreate([ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => 'Password123', ]);
$this->assertEquals('John Doe', $dto->name); $this->assertEquals('john@example.com', $dto->email); }}Conditional Property Tests
Section titled “Conditional Property Tests”Test Visibility
Section titled “Test Visibility”class UserDTOTest extends TestCase{ public function test_hides_email_when_not_authenticated(): void { Auth::logout();
$dto = UserDTO::fromArray([ 'id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', ]);
$array = $dto->toArray();
$this->assertArrayNotHasKey('email', $array); }
public function test_shows_email_when_authenticated(): void { $user = User::factory()->create(); Auth::login($user);
$dto = UserDTO::fromArray([ 'id' => 1, 'name' => 'John Doe', 'email' => 'john@example.com', ]);
$array = $dto->toArray();
$this->assertArrayHasKey('email', $array); $this->assertEquals('john@example.com', $array['email']); }
public function test_shows_admin_data_for_admin(): void { $admin = User::factory()->create(['role' => 'admin']); Auth::login($admin);
$dto = UserDTO::fromArray([ 'id' => 1, 'name' => 'John Doe', 'adminData' => ['key' => 'value'], ]);
$array = $dto->toArray();
$this->assertArrayHasKey('adminData', $array); }}Type Casting Tests
Section titled “Type Casting Tests”Test Casts
Section titled “Test Casts”class UserDTOTest extends TestCase{ public function test_casts_date_to_carbon(): void { $dto = UserDTO::fromArray([ 'id' => 1, 'name' => 'John Doe', 'createdAt' => '2024-01-15 10:30:00', ]);
$this->assertInstanceOf(Carbon::class, $dto->createdAt); $this->assertEquals('2024-01-15', $dto->createdAt->format('Y-m-d')); }
public function test_casts_enum(): void { $dto = UserDTO::fromArray([ 'id' => 1, 'name' => 'John Doe', 'role' => 'admin', ]);
$this->assertInstanceOf(UserRole::class, $dto->role); $this->assertEquals(UserRole::ADMIN, $dto->role); }}Integration Tests
Section titled “Integration Tests”Laravel Controller Tests
Section titled “Laravel Controller Tests”class UserControllerTest extends TestCase{ use RefreshDatabase;
public function test_creates_user_with_dto(): void { $response = $this->postJson('/api/users', [ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => 'Password123', ]);
$response->assertStatus(201); $response->assertJson([ 'name' => 'John Doe', 'email' => 'john@example.com', ]);
$this->assertDatabaseHas('users', [ 'email' => 'john@example.com', ]); }
public function test_validates_user_input(): void { $response = $this->postJson('/api/users', [ 'name' => 'John Doe', 'email' => 'invalid-email', ]);
$response->assertStatus(422); $response->assertJsonValidationErrors(['email']); }}Symfony Controller Tests
Section titled “Symfony Controller Tests”class UserControllerTest extends WebTestCase{ public function testCreateUser(): void { $client = static::createClient();
$client->request('POST', '/api/users', [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode([ 'name' => 'John Doe', 'email' => 'john@example.com', 'password' => 'Password123', ]));
$this->assertResponseIsSuccessful(); $this->assertResponseStatusCodeSame(201); }}Performance Tests
Section titled “Performance Tests”Benchmark DTO Creation
Section titled “Benchmark DTO Creation”class UserDTOPerformanceTest extends TestCase{ public function test_creates_1000_dtos_quickly(): void { $start = microtime(true);
for ($i = 0; $i < 1000; $i++) { UserDTO::fromArray([ 'id' => $i, 'name' => "User $i", 'email' => "user$i@example.com", ]); }
$duration = microtime(true) - $start;
$this->assertLessThan(1.0, $duration, 'Creating 1000 DTOs should take less than 1 second'); }}Best Practices
Section titled “Best Practices”1. Test All Validation Rules
Section titled “1. Test All Validation Rules”// ✅ Good - Test each validation rulepublic function test_validates_email_format(): void { ... }public function test_validates_password_length(): void { ... }public function test_validates_required_fields(): void { ... }2. Test Edge Cases
Section titled “2. Test Edge Cases”// ✅ Good - Test edge casespublic function test_handles_null_values(): void { ... }public function test_handles_empty_arrays(): void { ... }public function test_handles_special_characters(): void { ... }3. Use Data Providers
Section titled “3. Use Data Providers”/** * @dataProvider invalidEmailProvider */public function test_validates_email_format(string $email): void{ $this->expectException(ValidationException::class);
CreateUserDTO::validateAndCreate([ 'name' => 'John Doe', 'email' => $email, 'password' => 'Password123', ]);}
public function invalidEmailProvider(): array{ return [ ['invalid'], ['@example.com'], ['user@'], ['user @example.com'], ];}See Also
Section titled “See Also”- Creating DTOs - DTO creation guide
- Validation - Validation guide
- Type Casting - Type casting guide