# Testing: Getting Started
- [Introduction](#introduction)
- [Setting Up Tests](#setting-up-tests)
- [Writing Tests](#writing-tests)
- [Controller Testing](#controller-testing)
- [Model Testing](#model-testing)
- [Mock HTTP Responses](#mock-http-responses)
- [Running Tests](#running-tests)
## Introduction
Magic is built with testing in mind from the ground up. The framework provides a testing foundation that works seamlessly with Dart's built-in `test` package, along with helpers for testing controllers, models, and HTTP interactions.
## Setting Up Tests
### Directory Structure
```
test/
├── unit/
│ ├── models/
│ │ └── user_test.dart
│ └── controllers/
│ └── auth_controller_test.dart
├── feature/
│ └── authentication_test.dart
└── test_helper.dart
```
### Test Helper
Create a `test/test_helper.dart` for common setup:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:fluttersdk_magic/fluttersdk_magic.dart';
Future setupTestEnvironment() async {
TestWidgetsFlutterBinding.ensureInitialized();
await Magic.init(
envFileName: '.env.testing',
configFactories: [
() => appConfig,
() => testDatabaseConfig,
],
);
}
Map get testDatabaseConfig => {
'database': {
'default': 'sqlite',
'connections': {
'sqlite': {
'driver': 'sqlite',
'database': ':memory:', // In-memory for tests
},
},
},
};
```
## Writing Tests
### Basic Test Structure
```dart
import 'package:flutter_test/flutter_test.dart';
import '../test_helper.dart';
void main() {
setUpAll(() async {
await setupTestEnvironment();
});
group('User', () {
test('can create a user', () async {
final user = User()
..fill({
'name': 'John Doe',
'email': 'john@example.com',
});
await user.save();
expect(user.id, isNotNull);
expect(user.name, equals('John Doe'));
});
test('validates email format', () {
final validator = EmailValidator();
expect(validator.passes('john@example.com'), isTrue);
expect(validator.passes('invalid-email'), isFalse);
});
});
}
```
## Controller Testing
Test controllers by creating instances and calling methods:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
void main() {
late AuthController controller;
setUp(() {
controller = AuthController();
});
tearDown(() {
controller.dispose();
});
group('AuthController', () {
test('login sets loading state', () async {
expect(controller.isLoading, isFalse);
// Start login (will fail due to no mock, but tests state)
controller.login({'email': 'test@test.com', 'password': 'password'});
expect(controller.isLoading, isTrue);
});
test('clearErrors removes all validation errors', () {
controller.setError('email', 'Invalid email');
expect(controller.hasError('email'), isTrue);
controller.clearErrors();
expect(controller.hasError('email'), isFalse);
});
});
}
```
### Testing State Changes
```dart
test('setSuccess updates state correctly', () {
final controller = TaskController();
expect(controller.isLoading, isFalse);
expect(controller.isSuccess, isFalse);
controller.setLoading();
expect(controller.isLoading, isTrue);
controller.setSuccess([Task(name: 'Test')]);
expect(controller.isSuccess, isTrue);
expect(controller.data, isNotEmpty);
});
```
## Model Testing
Test models using factories for consistent data:
```dart
import 'package:flutter_test/flutter_test.dart';
void main() {
setUpAll(() async {
await setupTestEnvironment();
await Migrator().run([CreateUsersTable()]);
});
group('User Model', () {
test('creates user with factory', () async {
final users = await UserFactory().count(5).create();
expect(users.length, equals(5));
expect(users.first.id, isNotNull);
});
test('finds user by id', () async {
final created = (await UserFactory().create()).first;
final found = await User.find(created.id);
expect(found, isNotNull);
expect(found!.email, equals(created.email));
});
test('updates user attributes', () async {
final user = (await UserFactory().create()).first;
user.name = 'Updated Name';
await user.save();
final fresh = await User.find(user.id);
expect(fresh!.name, equals('Updated Name'));
});
test('deletes user', () async {
final user = (await UserFactory().create()).first;
final id = user.id;
await user.delete();
final found = await User.find(id);
expect(found, isNull);
});
});
}
```
## Mock HTTP Responses
Use mock HTTP responses for testing API interactions:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockHttpClient extends Mock implements HttpClient {}
void main() {
late MockHttpClient mockHttp;
late AuthController controller;
setUp(() {
mockHttp = MockHttpClient();
// Inject mock into Http facade
Http.setTestClient(mockHttp);
controller = AuthController();
});
test('login success redirects to dashboard', () async {
// Arrange
when(() => mockHttp.post('/login', data: any(named: 'data')))
.thenAnswer((_) async => MagicResponse(
statusCode: 200,
body: {
'token': 'test-token',
'user': {'id': 1, 'name': 'John'},
},
));
// Act
await controller.login({
'email': 'john@example.com',
'password': 'password',
});
// Assert
expect(Auth.check(), isTrue);
});
test('login failure shows error', () async {
when(() => mockHttp.post('/login', data: any(named: 'data')))
.thenAnswer((_) async => MagicResponse(
statusCode: 401,
body: {'message': 'Invalid credentials'},
));
await controller.login({
'email': 'john@example.com',
'password': 'wrong',
});
expect(controller.isError, isTrue);
});
}
```
## Running Tests
### Run All Tests
```bash
flutter test
```
### Run Specific Test File
```bash
flutter test test/unit/models/user_test.dart
```
### Run With Coverage
```bash
flutter test --coverage
```
### Test Output
```bash
flutter test --reporter=expanded
```
> [!TIP]
> Use the `:memory:` database driver for faster tests that don't persist to disk.