# HTTP Tests - [Introduction](#introduction) - [Making Requests](#making-requests) - [Testing JSON APIs](#testing-json-apis) - [Testing Responses](#testing-responses) - [Mocking HTTP](#mocking-http) - [Authentication Testing](#authentication-testing) ## Introduction Magic provides utilities for testing HTTP interactions in your application. Test your API calls, mock responses, and verify your application handles various HTTP scenarios correctly. ## Making Requests ### Test HTTP Client Set up a mock HTTP client for testing: ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; class MockHttpClient extends Mock implements MagicHttpClient {} void main() { late MockHttpClient mockHttp; setUp(() { mockHttp = MockHttpClient(); Http.setTestClient(mockHttp); }); tearDown(() { Http.resetClient(); }); } ``` ## Testing JSON APIs ### Mocking GET Requests ```dart test('fetches users from API', () async { // Arrange when(() => mockHttp.get('/users')) .thenAnswer((_) async => MagicResponse( statusCode: 200, body: [ {'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Jane'}, ], )); // Act final users = await UserController().fetchUsers(); // Assert expect(users.length, equals(2)); expect(users.first.name, equals('John')); verify(() => mockHttp.get('/users')).called(1); }); ``` ### Mocking POST Requests ```dart test('creates user via API', () async { final userData = {'name': 'John', 'email': 'john@example.com'}; when(() => mockHttp.post('/users', data: userData)) .thenAnswer((_) async => MagicResponse( statusCode: 201, body: {'id': 1, ...userData}, )); final user = await UserController().createUser(userData); expect(user.id, equals(1)); expect(user.name, equals('John')); }); ``` ## Testing Responses ### Testing Success Responses ```dart test('handles successful response', () async { when(() => mockHttp.get('/status')) .thenAnswer((_) async => MagicResponse( statusCode: 200, body: {'status': 'ok'}, )); final response = await Http.get('/status'); expect(response.successful, isTrue); expect(response['status'], equals('ok')); }); ``` ### Testing Error Responses ```dart test('handles 404 not found', () async { when(() => mockHttp.get('/users/999')) .thenAnswer((_) async => MagicResponse( statusCode: 404, body: {'message': 'User not found'}, )); final response = await Http.get('/users/999'); expect(response.notFound, isTrue); expect(response.errorMessage, equals('User not found')); }); ``` ### Testing Validation Errors ```dart test('handles validation errors', () async { when(() => mockHttp.post('/users', data: any(named: 'data'))) .thenAnswer((_) async => MagicResponse( statusCode: 422, body: { 'message': 'The given data was invalid.', 'errors': { 'email': ['The email field is required.'], 'password': ['The password must be at least 8 characters.'], }, }, )); final response = await Http.post('/users', data: {}); expect(response.isValidationError, isTrue); expect(response.errors['email'], contains('The email field is required.')); }); ``` ## Mocking HTTP ### Creating Mock Responses ```dart MagicResponse mockSuccess(dynamic body) { return MagicResponse(statusCode: 200, body: body); } MagicResponse mockValidationError(Map> errors) { return MagicResponse( statusCode: 422, body: { 'message': 'Validation failed', 'errors': errors, }, ); } MagicResponse mockUnauthorized() { return MagicResponse( statusCode: 401, body: {'message': 'Unauthenticated'}, ); } ``` ### Using Mock Helpers ```dart test('controller handles validation error', () async { when(() => mockHttp.post('/register', data: any(named: 'data'))) .thenAnswer((_) async => mockValidationError({ 'email': ['Email already taken'], })); final controller = AuthController(); await controller.register({'email': 'existing@test.com'}); expect(controller.hasError('email'), isTrue); expect(controller.getError('email'), equals('Email already taken')); }); ``` ## Authentication Testing ### Testing Login Flow ```dart test('successful login stores token and redirects', () async { when(() => mockHttp.post('/login', data: any(named: 'data'))) .thenAnswer((_) async => MagicResponse( statusCode: 200, body: { 'token': 'test-token', 'user': {'id': 1, 'name': 'John'}, }, )); final controller = AuthController(); await controller.login({ 'email': 'john@example.com', 'password': 'password', }); expect(Auth.check(), isTrue); expect(Auth.user()?.name, equals('John')); }); ``` ### Testing Token Refresh ```dart test('refreshes token on 401', () async { // First request fails with 401 when(() => mockHttp.get('/protected')) .thenAnswer((_) async => MagicResponse(statusCode: 401)); // Refresh succeeds when(() => mockHttp.post('/refresh', data: any(named: 'data'))) .thenAnswer((_) async => MagicResponse( statusCode: 200, body: {'token': 'new-token'}, )); final success = await Auth.refreshToken(); expect(success, isTrue); }); ``` ### Testing Protected Routes ```dart test('redirects unauthenticated user to login', () async { // Ensure user is logged out await Auth.logout(); final middleware = EnsureAuthenticated(); var redirected = false; await middleware.handle(() { // Should not reach here fail('Should have redirected'); }); // Verify redirect happened // (actual implementation would check navigation) }); ``` > [!TIP] > Create a `test/mocks/` directory with reusable mock classes and response factories to keep tests DRY.