platform/doc/testing_package_specification.md
2024-11-25 20:33:45 -07:00

466 lines
12 KiB
Markdown

# Testing Package Specification
## Overview
The Testing package provides a robust testing framework that matches Laravel's testing functionality. It supports test case base classes, assertions, database testing, HTTP testing, and mocking while integrating with our Container and Event packages.
> **Related Documentation**
> - See [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md) for implementation status
> - See [Foundation Integration Guide](foundation_integration_guide.md) for integration patterns
> - See [Testing Guide](testing_guide.md) for testing approaches
> - See [Getting Started Guide](getting_started.md) for development setup
> - See [Container Package Specification](container_package_specification.md) for dependency injection
> - See [Events Package Specification](events_package_specification.md) for event testing
## Core Features
### 1. Test Case
```dart
/// Base test case class
abstract class TestCase {
/// Container instance
late Container container;
/// Application instance
late Application app;
/// Event dispatcher
late EventDispatcherContract events;
/// Sets up test case
@override
void setUp() {
container = Container();
app = Application(container);
events = container.make<EventDispatcherContract>();
setUpApplication();
registerServices();
}
/// Sets up application
void setUpApplication() {
app.singleton<Application>((c) => app);
app.singleton<Container>((c) => container);
app.singleton<EventDispatcherContract>((c) => events);
}
/// Registers test services
void registerServices() {}
/// Creates test instance
T make<T>([dynamic parameters]) {
return container.make<T>(parameters);
}
/// Runs test in transaction
Future<T> transaction<T>(Future<T> Function() callback) async {
var db = container.make<DatabaseManager>();
return await db.transaction(callback);
}
/// Refreshes database
Future<void> refreshDatabase() async {
await artisan.call('migrate:fresh');
}
/// Seeds database
Future<void> seed([String? class]) async {
await artisan.call('db:seed', [
if (class != null) '--class=$class'
]);
}
}
```
### 2. HTTP Testing
```dart
/// HTTP test case
abstract class HttpTestCase extends TestCase {
/// HTTP client
late TestClient client;
@override
void setUp() {
super.setUp();
client = TestClient(app);
}
/// Makes GET request
Future<TestResponse> get(String uri, {
Map<String, String>? headers,
Map<String, dynamic>? query
}) {
return client.get(uri, headers: headers, query: query);
}
/// Makes POST request
Future<TestResponse> post(String uri, {
Map<String, String>? headers,
dynamic body
}) {
return client.post(uri, headers: headers, body: body);
}
/// Makes PUT request
Future<TestResponse> put(String uri, {
Map<String, String>? headers,
dynamic body
}) {
return client.put(uri, headers: headers, body: body);
}
/// Makes DELETE request
Future<TestResponse> delete(String uri, {
Map<String, String>? headers
}) {
return client.delete(uri, headers: headers);
}
/// Acts as user
Future<void> actingAs(User user) async {
await auth.login(user);
}
}
/// Test HTTP client
class TestClient {
/// Application instance
final Application app;
TestClient(this.app);
/// Makes HTTP request
Future<TestResponse> request(
String method,
String uri, {
Map<String, String>? headers,
dynamic body,
Map<String, dynamic>? query
}) async {
var request = Request(method, uri)
..headers.addAll(headers ?? {})
..body = body
..uri = uri.replace(queryParameters: query);
var response = await app.handle(request);
return TestResponse(response);
}
}
/// Test HTTP response
class TestResponse {
/// Response instance
final Response response;
TestResponse(this.response);
/// Asserts response status
void assertStatus(int status) {
expect(response.statusCode, equals(status));
}
/// Asserts response is OK
void assertOk() {
assertStatus(200);
}
/// Asserts response is redirect
void assertRedirect([String? location]) {
expect(response.statusCode, inInclusiveRange(300, 399));
if (location != null) {
expect(response.headers['location'], equals(location));
}
}
/// Asserts response contains JSON
void assertJson(Map<String, dynamic> json) {
expect(response.json(), equals(json));
}
/// Asserts response contains text
void assertSee(String text) {
expect(response.body, contains(text));
}
}
```
### 3. Database Testing
```dart
/// Database test case
abstract class DatabaseTestCase extends TestCase {
/// Database manager
late DatabaseManager db;
@override
void setUp() {
super.setUp();
db = container.make<DatabaseManager>();
}
/// Seeds database
Future<void> seed(String seeder) async {
await artisan.call('db:seed', ['--class=$seeder']);
}
/// Asserts database has record
Future<void> assertDatabaseHas(
String table,
Map<String, dynamic> data
) async {
var count = await db.table(table)
.where(data)
.count();
expect(count, greaterThan(0));
}
/// Asserts database missing record
Future<void> assertDatabaseMissing(
String table,
Map<String, dynamic> data
) async {
var count = await db.table(table)
.where(data)
.count();
expect(count, equals(0));
}
/// Asserts database count
Future<void> assertDatabaseCount(
String table,
int count
) async {
var actual = await db.table(table).count();
expect(actual, equals(count));
}
}
```
### 4. Event Testing
```dart
/// Event test case
abstract class EventTestCase extends TestCase {
/// Fake event dispatcher
late FakeEventDispatcher events;
@override
void setUp() {
super.setUp();
events = FakeEventDispatcher();
container.instance<EventDispatcherContract>(events);
}
/// Asserts event dispatched
void assertDispatched(Type event, [Function? callback]) {
expect(events.dispatched(event), isTrue);
if (callback != null) {
var dispatched = events.dispatched(event, callback);
expect(dispatched, isTrue);
}
}
/// Asserts event not dispatched
void assertNotDispatched(Type event) {
expect(events.dispatched(event), isFalse);
}
/// Asserts nothing dispatched
void assertNothingDispatched() {
expect(events.hasDispatched(), isFalse);
}
}
/// Fake event dispatcher
class FakeEventDispatcher implements EventDispatcherContract {
/// Dispatched events
final List<dynamic> _events = [];
@override
Future<void> dispatch<T>(T event) async {
_events.add(event);
}
/// Checks if event dispatched
bool dispatched(Type event, [Function? callback]) {
var dispatched = _events.whereType<Type>();
if (dispatched.isEmpty) return false;
if (callback == null) return true;
return dispatched.any((e) => callback(e));
}
/// Checks if any events dispatched
bool hasDispatched() => _events.isNotEmpty;
}
```
## Integration Examples
### 1. HTTP Testing
```dart
class UserTest extends HttpTestCase {
test('creates user', () async {
var response = await post('/users', body: {
'name': 'John Doe',
'email': 'john@example.com'
});
response.assertStatus(201);
await assertDatabaseHas('users', {
'email': 'john@example.com'
});
});
test('requires authentication', () async {
var user = await User.factory().create();
await actingAs(user);
var response = await get('/dashboard');
response.assertOk();
});
}
```
### 2. Database Testing
```dart
class OrderTest extends DatabaseTestCase {
test('creates order', () async {
await seed(ProductSeeder);
var order = await Order.create({
'product_id': 1,
'quantity': 5
});
await assertDatabaseHas('orders', {
'id': order.id,
'quantity': 5
});
});
}
```
### 3. Event Testing
```dart
class PaymentTest extends EventTestCase {
test('dispatches payment events', () async {
var payment = await processPayment(order);
assertDispatched(PaymentProcessed, (event) {
return event.payment.id == payment.id;
});
});
}
```
## Testing
```dart
void main() {
group('HTTP Testing', () {
test('makes requests', () async {
var client = TestClient(app);
var response = await client.get('/users');
expect(response.statusCode, equals(200));
expect(response.json(), isA<List>());
});
test('handles authentication', () async {
var case = UserTest();
await case.setUp();
await case.actingAs(user);
var response = await case.get('/profile');
response.assertOk();
});
});
group('Database Testing', () {
test('seeds database', () async {
var case = OrderTest();
await case.setUp();
await case.seed(ProductSeeder);
await case.assertDatabaseCount('products', 10);
});
});
}
```
## Next Steps
1. Implement core testing features
2. Add HTTP testing
3. Add database testing
4. Add event testing
5. Write tests
6. Add examples
## Development Guidelines
### 1. Getting Started
Before implementing testing features:
1. Review [Getting Started Guide](getting_started.md)
2. Check [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
3. Follow [Testing Guide](testing_guide.md)
4. Use [Foundation Integration Guide](foundation_integration_guide.md)
5. Review [Container Package Specification](container_package_specification.md)
6. Review [Events Package Specification](events_package_specification.md)
### 2. Implementation Process
For each testing feature:
1. Write tests following [Testing Guide](testing_guide.md)
2. Implement following Laravel patterns
3. Document following [Getting Started Guide](getting_started.md#documentation)
4. Integrate following [Foundation Integration Guide](foundation_integration_guide.md)
### 3. Quality Requirements
All implementations must:
1. Pass all tests (see [Testing Guide](testing_guide.md))
2. Meet Laravel compatibility requirements
3. Follow integration patterns (see [Foundation Integration Guide](foundation_integration_guide.md))
4. Support dependency injection (see [Container Package Specification](container_package_specification.md))
5. Support event testing (see [Events Package Specification](events_package_specification.md))
### 4. Integration Considerations
When implementing testing features:
1. Follow patterns in [Foundation Integration Guide](foundation_integration_guide.md)
2. Ensure Laravel compatibility per [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md)
3. Use testing approaches from [Testing Guide](testing_guide.md)
4. Follow development setup in [Getting Started Guide](getting_started.md)
### 5. Performance Guidelines
Testing system must:
1. Execute tests efficiently
2. Support parallel testing
3. Handle large test suites
4. Manage test isolation
5. Meet performance targets in [Laravel Compatibility Roadmap](laravel_compatibility_roadmap.md#performance-benchmarks)
### 6. Testing Requirements
Testing package tests must:
1. Cover all testing features
2. Test HTTP assertions
3. Verify database testing
4. Check event assertions
5. Follow patterns in [Testing Guide](testing_guide.md)
### 7. Documentation Requirements
Testing documentation must:
1. Explain testing patterns
2. Show assertion examples
3. Cover test organization
4. Include performance tips
5. Follow standards in [Getting Started Guide](getting_started.md#documentation)