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() and tearDown() for common setup
  • Mock external dependencies
  • Test edge cases and error conditions
  • Keep tests fast and independent
  • Use fakeAsync for time-based tests