Documentation Index
Fetch the complete documentation index at: https://mintlify.com/khode-io/nest-dart/llms.txt
Use this file to discover all available pages before exploring further.
Testing Strategies
Nest Dart provides powerful testing capabilities through container isolation and module-based dependency injection.Container Reset for Tests
TheApplicationContainer provides a reset() method essential for test isolation:
import 'package:test/test.dart';
import 'package:nest_core/nest_core.dart';
import 'package:your_app/app_module.dart';
void main() {
late ApplicationContainer container;
setUp(() async {
container = ApplicationContainer();
await container.registerModule(AppModule());
});
tearDown(() async {
await container.reset();
});
test('should initialize services correctly', () async {
final userService = container.get<UserService>();
expect(userService, isNotNull);
});
}
How Reset Works
Frompackages/nest_core/lib/src/container.dart:260-287:
Future<void> reset() async {
// Destroy modules in reverse order of initialization
final modulesToDestroy = List<Module>.from(_initializedModules.reversed);
for (final module in modulesToDestroy) {
try {
print('Destroying module: ${module.runtimeType}');
final scopedLocator = _ScopedGetIt(
_getIt,
_context,
module.runtimeType,
);
await module.onModuleDestroy(scopedLocator, _context);
print('Module destroyed successfully: ${module.runtimeType}');
} catch (e) {
print('Error destroying module ${module.runtimeType}: $e');
}
}
_getIt.reset();
_modules.clear();
_initializedModules.clear();
_context.moduleExports.clear();
_context.moduleImports.clear();
_context.serviceToModule.clear();
_context.globalServices.clear();
_isReady = false;
}
Modules are destroyed in reverse initialization order, ensuring dependencies are cleaned up before their dependents.
Testing Modules in Isolation
Test individual modules without initializing the entire application:import 'package:test/test.dart';
import 'package:nest_core/nest_core.dart';
import 'package:your_app/modules/user_module.dart';
import 'package:your_app/modules/database_module.dart';
void main() {
group('UserModule', () {
late ApplicationContainer container;
setUp(() async {
container = ApplicationContainer();
// Only register the modules needed for this test
await container.registerModule(
UserModule()..imports = [DatabaseModule()],
);
});
tearDown(() async {
await container.reset();
});
test('should provide UserService', () {
expect(container.isRegistered<UserService>(), isTrue);
});
test('should export UserService', () {
final userService = container.get<UserService>();
expect(userService, isA<UserService>());
});
});
}
Mocking Dependencies
Using Test Doubles
Create test modules with mocked services:import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
import 'package:nest_core/nest_core.dart';
class MockUserRepository extends Mock implements UserRepository {}
class MockDatabaseService extends Mock implements DatabaseService {}
class TestDatabaseModule extends Module {
final MockDatabaseService mockDatabase = MockDatabaseService();
@override
Future<void> providers(Locator locator) async {
locator.registerSingleton<DatabaseService>(mockDatabase);
}
@override
List<Type> get exports => [DatabaseService];
}
void main() {
group('UserService with mocked database', () {
late ApplicationContainer container;
late MockDatabaseService mockDatabase;
setUp(() async {
final testDbModule = TestDatabaseModule();
mockDatabase = testDbModule.mockDatabase;
container = ApplicationContainer();
await container.registerModule(
UserModule()..imports = [testDbModule],
);
});
tearDown(() async {
await container.reset();
});
test('should fetch users from database', () async {
// Arrange
when(() => mockDatabase.query('SELECT * FROM users'))
.thenAnswer((_) async => [
{'id': 1, 'name': 'John'},
{'id': 2, 'name': 'Jane'},
]);
// Act
final userService = container.get<UserService>();
final users = await userService.getAllUsers();
// Assert
expect(users, hasLength(2));
verify(() => mockDatabase.query('SELECT * FROM users')).called(1);
});
});
}
Factory-Based Mocking
Inject mock factories for more control:class TestUserModule extends Module {
final UserRepository mockRepository;
TestUserModule(this.mockRepository);
@override
Future<void> providers(Locator locator) async {
locator.registerSingleton<UserRepository>(mockRepository);
locator.registerFactory<UserService>(
() => UserService(locator.get<UserRepository>()),
);
}
@override
List<Type> get exports => [UserService];
}
void main() {
test('UserService with custom mock', () async {
final mockRepo = MockUserRepository();
when(() => mockRepo.findById(1))
.thenAnswer((_) async => User(id: 1, name: 'Test'));
final container = ApplicationContainer();
await container.registerModule(TestUserModule(mockRepo));
final userService = container.get<UserService>();
final user = await userService.getUser(1);
expect(user.name, equals('Test'));
await container.reset();
});
}
Testing Route Handlers (Dart Frog)
Test Dart Frog routes with mocked dependencies:import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
import 'package:nest_frog/nest_frog.dart';
class MockRequestContext extends Mock implements RequestContext {}
void main() {
group('GET /users', () {
late ApplicationContainer container;
late MockUserService mockUserService;
setUp(() async {
mockUserService = MockUserService();
final testModule = TestModule(mockUserService);
await Modular.initialize(testModule);
});
tearDown(() async {
await Modular.reset();
});
test('responds with list of users', () async {
// Arrange
when(() => mockUserService.getAllUsers())
.thenAnswer((_) async => [
User(id: 1, name: 'John'),
User(id: 2, name: 'Jane'),
]);
final context = MockRequestContext();
// Act
final response = await onRequest(context);
// Assert
expect(response.statusCode, equals(HttpStatus.ok));
final body = await response.json();
expect(body, hasLength(2));
});
});
}
Testing Module Lifecycle
TestonModuleInit and onModuleDestroy hooks:
class TestableModule extends Module {
bool isInitialized = false;
bool isDestroyed = false;
@override
Future<void> onModuleInit(Locator locator, ModuleContext context) async {
isInitialized = true;
// Simulate initialization work
await Future.delayed(Duration(milliseconds: 100));
}
@override
Future<void> onModuleDestroy(Locator locator, ModuleContext context) async {
isDestroyed = true;
// Simulate cleanup work
await Future.delayed(Duration(milliseconds: 50));
}
}
void main() {
test('module lifecycle hooks are called', () async {
final module = TestableModule();
final container = ApplicationContainer();
// Test initialization
await container.registerModule(module);
expect(module.isInitialized, isTrue);
expect(module.isDestroyed, isFalse);
// Test destruction
await container.reset();
expect(module.isDestroyed, isTrue);
});
}
Testing Service Exports and Access Control
Verify that modules properly enforce export restrictions:import 'package:nest_core/nest_core.dart';
import 'package:test/test.dart';
class PrivateService {}
class PublicService {}
class ProviderModule extends Module {
@override
Future<void> providers(Locator locator) async {
locator.registerSingleton<PrivateService>(PrivateService());
locator.registerSingleton<PublicService>(PublicService());
}
@override
List<Type> get exports => [PublicService]; // Only export PublicService
}
class ConsumerModule extends Module {
@override
List<Module> get imports => [ProviderModule()];
@override
Future<void> providers(Locator locator) async {
// This should work - PublicService is exported
final publicService = locator.get<PublicService>();
// This should throw - PrivateService is not exported
// final privateService = locator.get<PrivateService>();
}
}
void main() {
test('should enforce export restrictions', () async {
final container = ApplicationContainer();
await container.registerModule(ConsumerModule());
// Public service should be accessible
expect(
() => container.get<PublicService>(),
returnsNormally,
);
// Private service should throw
expect(
() => container.get<PrivateService>(),
throwsA(isA<ServiceNotExportedException>()),
);
await container.reset();
});
}
Always verify export restrictions in tests to ensure modules maintain proper encapsulation.
Integration Testing
Test full application flow with real modules:import 'package:test/test.dart';
import 'package:nest_core/nest_core.dart';
import 'package:your_app/app_module.dart';
void main() {
group('Integration Tests', () {
late ApplicationContainer container;
setUp(() async {
container = ApplicationContainer();
await container.registerModule(AppModule());
await container.waitUntilReady();
});
tearDown(() async {
await container.reset();
});
test('full user creation flow', () async {
// Get services from container
final userService = container.get<UserService>();
final emailService = container.get<EmailService>();
// Create user
final user = await userService.createUser(
name: 'John Doe',
email: 'john@example.com',
);
// Verify user was created
expect(user.id, isNotNull);
expect(user.name, equals('John Doe'));
// Verify welcome email was sent
final sentEmails = emailService.getSentEmails();
expect(sentEmails, hasLength(1));
expect(sentEmails.first.to, equals('john@example.com'));
});
});
}
Best Practices
1. Always Reset Container
void main() {
setUp(() async {
// Initialize fresh container for each test
container = ApplicationContainer();
await container.registerModule(TestModule());
});
tearDown(() async {
// Always reset to ensure test isolation
await container.reset();
});
}
2. Use Test Modules
// Create dedicated test modules
class TestModule extends Module {
final MockDatabaseService mockDb;
TestModule(this.mockDb);
@override
Future<void> providers(Locator locator) async {
locator.registerSingleton<DatabaseService>(mockDb);
}
}
3. Test Lifecycle Hooks
test('modules initialize in correct order', () async {
final initOrder = <String>[];
final moduleA = TrackingModule('A', initOrder);
final moduleB = TrackingModule('B', initOrder);
final container = ApplicationContainer();
await container.registerModule(
moduleA..imports = [moduleB],
);
// Dependencies should initialize first
expect(initOrder, equals(['B', 'A']));
await container.reset();
});
4. Verify Export Behavior
test('services are properly exported', () async {
final container = ApplicationContainer();
await container.registerModule(YourModule());
// Verify exported services are accessible
expect(() => container.get<ExportedService>(), returnsNormally);
// Verify private services are not accessible
expect(
() => container.get<PrivateService>(),
throwsA(isA<ServiceNotExportedException>()),
);
await container.reset();
});
Use the
isRegistered<T>() method to check if a service is available before attempting to access it in tests.