Testing
AngularDart provides comprehensive testing utilities through the angulardart_test
package. You can write unit tests, component tests, and integration tests.
Setup
Add test dependencies:
dev_dependencies:
angulardart_test: '>=6.0.0 <7.0.0'
test: '>=1.31.0 <2.0.0'
Unit Testing Services
Test services without any AngularDart dependencies:
import 'package:test/test.dart';
import 'package:my_app/src/services/user_service.dart';
void main() {
group('UserService', () {
late UserService service;
setUp(() {
service = UserService();
});
test('should return empty list initially', () {
expect(service.getUsers(), isEmpty);
});
test('should add user', () {
final user = User(id: 1, name: 'Alice');
service.addUser(user);
expect(service.getUsers(), contains(user));
});
test('should delete user', () {
final user = User(id: 1, name: 'Alice');
service.addUser(user);
service.deleteUser(1);
expect(service.getUsers(), isEmpty);
});
});
}
Component Testing
Basic Component Test
import 'package:angulardart_test/angulardart_test.dart';
import 'package:test/test.dart';
import 'package:my_app/src/components/my_component.dart';
import 'package:my_app/src/components/my_component.template.dart' as ng;
void main() {
group('MyComponent', () {
late ComponentFixture<MyComponent> fixture;
late MyComponent component;
setUp(() async {
await setUpInjector();
fixture = await TestBed.createComponent(ng.MyComponentNgFactory);
component = fixture.componentInstance;
});
tearDown(() async {
await disposeAnyResetters();
});
test('should create component', () {
expect(component).isNotNull;
});
test('should display title', () {
fixture.detectChanges();
final element = fixture.debugElement.query(By.css('h1'));
expect(element.nativeElement.text, equals('Hello World'));
});
});
}
Testing User Interactions
test('should increment counter on button click', () async {
fixture.detectChanges();
final button = fixture.debugElement.query(By.css('button'));
await button.nativeElement.click();
fixture.detectChanges();
final counter = fixture.debugElement.query(By.css('.counter'));
expect(counter.nativeElement.text, equals('1'));
});
Testing Input Bindings
test('should display user name', () {
component.user = User(name: 'Alice');
fixture.detectChanges();
final element = fixture.debugElement.query(By.css('.user-name'));
expect(element.nativeElement.text, equals('Alice'));
});
Testing Output Events
test('should emit event on save', () async {
User? savedUser;
component.onSave.listen((user) => savedUser = user);
fixture.detectChanges();
final button = fixture.debugElement.query(By.css('.save-btn'));
await button.nativeElement.click();
expect(savedUser, isNotNull);
expect(savedUser!.name, equals('Test User'));
});
Testing Forms
test('should show validation error', () async {
fixture.detectChanges();
final input = fixture.debugElement.query(By.css('input[name="name"]'));
input.nativeElement.value = '';
input.nativeElement.dispatchEvent(Event('input'));
await fixture.update();
final error = fixture.debugElement.query(By.css('.error'));
expect(error, isNotNull);
expect(error.nativeElement.text, contains('required'));
});
Testing with Mock Services
@Injectable()
class MockUserService implements UserService {
List<User> _users = [];
@override
List<User> getUsers() => _users;
@override
void addUser(User user) => _users.add(user);
}
test('should use mock service', () async {
await setUpInjector()..bind(
UserService,
toValue: MockUserService(),
);
final fixture = await TestBed.createComponent(ng.UserListComponentNgFactory);
final component = fixture.componentInstance;
expect(component.users, isEmpty);
});
Async Testing
test('should load data asynchronously', () async {
final service = MockApiService();
component = MyComponent(service);
component.loadData();
await Future.delayed(Duration(milliseconds: 100));
expect(component.data, isNotNull);
expect(component.isLoading, isFalse);
});
Using fakeAsync
test('should delay before showing message', () {
fakeAsync(() {
component.showMessage();
expect(component.visible, isFalse);
tick(Duration(seconds: 3));
expect(component.visible, isTrue);
});
});
Integration Testing
Test multiple components working together:
test('full user flow', () async {
await setUpInjector();
final fixture = await TestBed.createComponent(ng.AppComponentNgFactory);
// Navigate to users page
final router = fixture.debugElement.injector.get(Router);
await router.navigate('/users');
fixture.detectChanges();
// Add a user
final input = fixture.debugElement.query(By.css('.user-input'));
input.nativeElement.value = 'Alice';
input.nativeElement.dispatchEvent(Event('input'));
final addButton = fixture.debugElement.query(By.css('.add-btn'));
await addButton.nativeElement.click();
fixture.detectChanges();
// Verify user is displayed
final userList = fixture.debugElement.query(By.css('.user-list'));
expect(userList.nativeElement.text, contains('Alice'));
});
Testing Directives
test('highlight directive should change background', () async {
await setUpInjector();
final fixture = await TestBed.createComponent(ng.TestComponentNgFactory);
fixture.detectChanges();
final element = fixture.debugElement.query(By.css('[highlight]'));
await element.nativeElement.dispatchEvent(MouseEvent('mouseenter'));
fixture.detectChanges();
expect(
element.nativeElement.style.backgroundColor,
equals('yellow'),
);
});
Best Practices
- Test behavior, not implementation
- Use descriptive test names
- Group related tests with
group() - Use
setUp()andtearDown()for common setup - Mock external dependencies
- Test edge cases and error conditions
- Keep tests fast and independent
- Use
fakeAsyncfor time-based tests