# HTTP Tests
- [Introduction](#introduction)
- [Faking Responses](#faking-responses)
- [URL Pattern Stubs](#url-pattern-stubs)
- [Callback Stubs](#callback-stubs)
- [Making Assertions](#making-assertions)
- [Preventing Stray Requests](#preventing-stray-requests)
- [Response Factory](#response-factory)
- [Unfaking](#unfaking)
- [Full Example](#full-example)
## Introduction
Magic provides a first-class HTTP faking API that lets you swap the real network driver with a `FakeNetworkDriver` during tests. All requests are recorded and no real network traffic is made. This approach is inspired by Laravel's `Http::fake()` and requires no third-party mocking libraries.
```dart
setUp(() {
MagicApp.reset();
Magic.flush();
Http.fake(); // Intercept all HTTP requests
});
tearDown(() {
Http.unfake(); // Restore real driver
});
```
## Faking Responses
Call `Http.fake()` before the code under test runs. When called with no arguments, every request returns a 200 response with empty data.
```dart
final fake = Http.fake();
final response = await Http.get('/users');
expect(response.successful, isTrue);
expect(response.data, equals({}));
```
### URL Pattern Stubs
Pass a `Map` to stub specific URL patterns. Patterns support `*` as a wildcard.
```dart
final fake = Http.fake({
'users/*': Http.response({'id': 1, 'name': 'Alice'}, 200),
'posts': Http.response([], 200),
'auth/login': Http.response({'token': 'test-token'}, 200),
});
final user = await Http.get('/users/42');
expect(user['name'], 'Alice');
final posts = await Http.get('/posts');
expect(posts.data, isEmpty);
```
Pattern matching is done after stripping the leading `/` so both `/users/42` and `users/42` match the pattern `users/*`.
### Callback Stubs
Pass a `FakeRequestHandler` — a function that receives a `MagicRequest` and returns a `MagicResponse` — for dynamic stubbing logic.
```dart
final fake = Http.fake((request) {
if (request.url.contains('admin')) {
return Http.response({'error': 'Forbidden'}, 403);
}
return Http.response({'ok': true}, 200);
});
final adminResponse = await Http.get('/admin/users');
expect(adminResponse.forbidden, isTrue);
final publicResponse = await Http.get('/users');
expect(publicResponse.successful, isTrue);
```
## Making Assertions
`Http.fake()` returns the `FakeNetworkDriver` instance. Use it to assert on recorded requests after the code under test runs.
### `assertSent`
Assert that at least one recorded request matches the predicate.
```dart
final fake = Http.fake();
await Http.post('/users', data: {'name': 'Bob'});
fake.assertSent((request) => request.url.contains('users'));
fake.assertSent(
(request) => request.method == 'POST' && request.url == '/users',
);
```
### `assertNotSent`
Assert that no recorded request matches the predicate.
```dart
fake.assertNotSent((request) => request.url.contains('payments'));
```
### `assertNothingSent`
Assert that no requests were made at all.
```dart
final fake = Http.fake();
// Code that should not touch the network
doSomethingLocal();
fake.assertNothingSent();
```
### `assertSentCount`
Assert an exact number of requests were recorded.
```dart
final fake = Http.fake();
await Http.get('/users');
await Http.get('/users/1');
fake.assertSentCount(2);
```
### `recorded`
Access the full list of recorded `(MagicRequest, MagicResponse)` pairs for custom assertions.
```dart
final fake = Http.fake();
await Http.post('/orders', data: {'item': 'Widget'});
final pair = fake.recorded.first;
expect(pair.$1.method, 'POST');
expect(pair.$2.statusCode, 200);
```
## Preventing Stray Requests
Call `preventStrayRequests()` on the fake driver to throw a `StrayRequestException` whenever an unmatched request is made. This ensures every HTTP call in your test has an explicit stub.
```dart
final fake = Http.fake({
'users': Http.response([], 200),
})..preventStrayRequests();
// This is fine — matched by stub
await Http.get('/users');
// This throws StrayRequestException — no matching stub
await Http.get('/notifications'); // throws!
```
> [!TIP]
> Enable `preventStrayRequests()` in tests that should be fully isolated from external services to catch accidental network calls early.
## Response Factory
`Http.response()` is a convenience factory for building `MagicResponse` objects to use as stub responses.
```dart
// 200 with empty data (default)
Http.response()
// 200 with a Map body
Http.response({'id': 1, 'name': 'Alice'})
// Custom status code
Http.response({'message': 'Not found'}, 404)
// List body
Http.response([{'id': 1}, {'id': 2}], 200)
// 422 validation errors
Http.response({
'message': 'The given data was invalid.',
'errors': {
'email': ['The email field is required.'],
},
}, 422)
```
## Unfaking
Call `Http.unfake()` in `tearDown` to remove the fake driver from the IoC container and restore the real network driver for subsequent tests.
```dart
tearDown(() {
Http.unfake();
});
```
Alternatively, you can call `fake.reset()` on the driver instance to clear recorded requests and stubs without restoring the real driver. This is useful when reusing the same fake across multiple test cases.
```dart
setUp(() {
MagicApp.reset();
Magic.flush();
});
// In a group where all tests share one fake:
final fake = Http.fake();
test('first test', () async {
await Http.get('/a');
fake.assertSentCount(1);
fake.reset(); // Clear for next test
});
test('second test', () async {
await Http.get('/b');
fake.assertSentCount(1);
});
```
## Full Example
A controller that fetches a user profile and the test verifying it:
```dart
// lib/controllers/profile_controller.dart
class ProfileController extends MagicController
with MagicStateMixin