-
Notifications
You must be signed in to change notification settings - Fork 27.3k
feat(test): test Library for Dart users #3289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| Contains helpers to run unit tests for angular2 components and injectables, | ||
| backed by the `package:test` [library](https://pub.dartlang.org/packages/test). | ||
|
|
||
| Usage | ||
| ----- | ||
|
|
||
|
|
||
| Update the dev dependencies in your `pubspec.yaml` to include the angular testing | ||
| and test packages: | ||
|
|
||
| ```yaml | ||
| dev_dependencies: | ||
| test: '^0.12.6' | ||
| angular2_testing: any | ||
|
|
||
| ``` | ||
|
|
||
| Then in your test files, use angular2_testing helpers in place of `setUp` and `test`: | ||
|
|
||
| ```dart | ||
| import 'package:test/test.dart'; | ||
| import 'package:angular2_testing/angular2_testing.dart'; | ||
|
|
||
| void main() { | ||
| // This must be called at the beginning of your tests. | ||
| initAngularTests(); | ||
|
|
||
| // Initialize the injection tokens you will use in your tests. | ||
| setUpProviders(() => [provide(MyToken, useValue: 'my string'), TestService]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style nit:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a preferred one? I think I've seen both in the repo already.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered my own question There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not aware of a clear favorite. I think either is fine as long as files are internally consistent. |
||
|
|
||
| // You can then get tokens from the injector via ngSetUp and ngTest. | ||
| ngSetUp((TestService testService) { | ||
| testService.initialize(); | ||
| }); | ||
|
|
||
| ngTest('can grab injected values', (@Inject(MyToken) token, TestService testService) { | ||
| expect(token, equals('my string')); | ||
| expect(testService.status, equals('ready')); | ||
| }); | ||
| } | ||
| ``` | ||
|
|
||
| Examples | ||
| -------- | ||
|
|
||
| A sample test is available in `test/angular2_testing_test.dart`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| library angular2_testing.angular2_testing; | ||
|
|
||
| import 'package:test/test.dart'; | ||
| import 'package:test/src/backend/invoker.dart'; | ||
| import 'package:test/src/backend/live_test.dart'; | ||
|
|
||
| import 'package:angular2/angular2.dart'; | ||
| import 'package:angular2/src/core/di/injector.dart' show Injector; | ||
| import 'package:angular2/src/core/di/metadata.dart' show InjectMetadata; | ||
| import 'package:angular2/src/core/di/exceptions.dart' show NoAnnotationError; | ||
| import 'package:angular2/platform/browser_static.dart' show BrowserDomAdapter; | ||
| import 'package:angular2/src/core/reflection/reflection.dart'; | ||
| import 'package:angular2/src/core/reflection/reflection_capabilities.dart'; | ||
| import 'package:angular2/src/testing/test_injector.dart'; | ||
| export 'package:angular2/src/testing/test_component_builder.dart'; | ||
| export 'package:angular2/src/testing/test_injector.dart' show inject; | ||
|
|
||
| /// One time initialization that must be done for Angular2 component | ||
| /// tests. Call before any test methods. | ||
| /// | ||
| /// Example: | ||
| /// | ||
| /// ``` | ||
| /// main() { | ||
| /// initAngularTests(); | ||
| /// group(...); | ||
| /// } | ||
| /// ``` | ||
| void initAngularTests() { | ||
| BrowserDomAdapter.makeCurrent(); | ||
| reflector.reflectionCapabilities = new ReflectionCapabilities(); | ||
| } | ||
|
|
||
| /// Allows overriding default bindings defined in test_injector.dart. | ||
| /// | ||
| /// The given function must return a list of DI providers. | ||
| /// | ||
| /// Example: | ||
| /// | ||
| /// ``` | ||
| /// setUpProviders(() => [ | ||
| /// provide(Compiler, useClass: MockCompiler), | ||
| /// provide(SomeToken, useValue: myValue), | ||
| /// ]); | ||
| /// ``` | ||
| void setUpProviders(Iterable<Provider> providerFactory()) { | ||
| setUp(() { | ||
| if (_currentInjector != null) { | ||
| throw 'setUpProviders was called after the injector had ' | ||
| 'been used in a setUp or test block. This invalidates the ' | ||
| 'test injector'; | ||
| } | ||
| _currentTestProviders.addAll(providerFactory()); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| dynamic _runInjectableFunction(Function fn) { | ||
| var params = reflector.parameters(fn); | ||
| List<dynamic> tokens = <dynamic>[]; | ||
| for (var param in params) { | ||
| var token = null; | ||
| for (var paramMetadata in param) { | ||
| if (paramMetadata is Type) { | ||
| token = paramMetadata; | ||
| } else if (paramMetadata is InjectMetadata) { | ||
| token = paramMetadata.token; | ||
| } | ||
| } | ||
| if (token == null) { | ||
| throw new NoAnnotationError(fn, params); | ||
| } | ||
| tokens.add(token); | ||
| } | ||
|
|
||
| if (_currentInjector == null) { | ||
| _currentInjector = createTestInjector(_currentTestProviders); | ||
| } | ||
| var injectFn = new FunctionWithParamTokens(tokens, fn, false); | ||
| return injectFn.execute(_currentInjector); | ||
| } | ||
|
|
||
| /// Use the test injector to get bindings and run a function. | ||
| /// | ||
| /// Example: | ||
| /// | ||
| /// ``` | ||
| /// ngSetUp((SomeToken token) { | ||
| /// token.init(); | ||
| /// }); | ||
| /// ``` | ||
| void ngSetUp(Function fn) { | ||
| setUp(() async { | ||
| await _runInjectableFunction(fn); | ||
| }); | ||
| } | ||
|
|
||
| /// Add a test which can use the test injector. | ||
| /// | ||
| /// Example: | ||
| /// | ||
| /// ``` | ||
| /// ngTest('description', (SomeToken token) { | ||
| /// expect(token, equals('expected')); | ||
| /// }); | ||
| /// ``` | ||
| void ngTest(String description, Function fn, | ||
| {String testOn, Timeout timeout, skip, Map<String, dynamic> onPlatform}) { | ||
| test(description, () async { | ||
| await _runInjectableFunction(fn); | ||
| }, testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform); | ||
| } | ||
|
|
||
| final _providersExpando = new Expando<List<Provider>>('Providers for the current test'); | ||
| final _injectorExpando = new Expando<Injector>('Angular Injector for the current test'); | ||
|
|
||
| List get _currentTestProviders { | ||
| if (_providersExpando[_currentTest] == null) { | ||
| return _providersExpando[_currentTest] = []; | ||
| } | ||
| return _providersExpando[_currentTest]; | ||
| } | ||
| Injector get _currentInjector => _injectorExpando[_currentTest]; | ||
| void set _currentInjector(Injector newInjector) { | ||
| _injectorExpando[_currentTest] = newInjector; | ||
| } | ||
|
|
||
| // TODO: warning, the Invoker.current.liveTest is not a settled API and is | ||
| // subject to change in future versions of package:test. | ||
| LiveTest get _currentTest => Invoker.current.liveTest; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| name: angular2_testing | ||
| environment: | ||
| sdk: '>=1.10.0 <2.0.0' | ||
| dependencies: | ||
| angular2: | ||
| path: ../../dist/dart/angular2 | ||
| dev_dependencies: | ||
| test: '^0.12.6' |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // Because Angular is using dart:html, we need these tests to run on an actual | ||
| // browser. This means that it should be run with `-p dartium` or `-p chrome`. | ||
| @TestOn('browser') | ||
| import 'package:angular2/angular2.dart' | ||
| show Component, View, NgFor, provide, Inject, Injectable, Optional; | ||
|
|
||
| import 'package:test/test.dart'; | ||
| import 'package:angular2_testing/angular2_testing.dart'; | ||
|
|
||
| // This is the component we will be testing. | ||
| @Component(selector: 'test-cmp') | ||
| @View(directives: const [NgFor]) | ||
| class TestComponent { | ||
| List<num> items; | ||
| TestComponent() { | ||
| this.items = [1, 2]; | ||
| } | ||
| } | ||
|
|
||
| @Injectable() | ||
| class TestService { | ||
| String status = 'not ready'; | ||
|
|
||
| init() { | ||
| this.status = 'ready'; | ||
| } | ||
| } | ||
|
|
||
| class MyToken {} | ||
|
|
||
| const TEMPLATE = | ||
| '<div><copy-me template=\'ng-for #item of items\'>{{item.toString()}};</copy-me></div>'; | ||
|
|
||
| void main() { | ||
| initAngularTests(); | ||
|
|
||
| setUpProviders(() => [provide(MyToken, useValue: 'my string'), TestService]); | ||
|
|
||
| test('normal function', () { | ||
| var string = 'foo,bar,baz'; | ||
| expect(string.split(','), equals(['foo', 'bar', 'baz'])); | ||
| }); | ||
|
|
||
| ngTest('can grab injected values', (@Inject(MyToken) token, TestService testService) { | ||
| expect(token, equals('my string')); | ||
| expect(testService.status, equals('not ready')); | ||
| }); | ||
|
|
||
| group('nested ngSetUp', () { | ||
| ngSetUp((TestService testService) { | ||
| testService.init(); | ||
| }); | ||
|
|
||
| ngTest('ngSetUp modifies injected services', (TestService testService) { | ||
| expect(testService.status, equals('ready')); | ||
| }); | ||
| }); | ||
|
|
||
| ngTest('create a component using the TestComponentBuilder', (TestComponentBuilder tcb) async { | ||
| var rootTC = await tcb | ||
| .overrideTemplate(TestComponent, TEMPLATE) | ||
| .createAsync(TestComponent); | ||
|
|
||
| rootTC.detectChanges(); | ||
| expect(rootTC.debugElement.nativeElement.text, equals('1;2;')); | ||
| }); | ||
|
|
||
| ngTest('should reflect added elements', (TestComponentBuilder tcb) async { | ||
| var rootTC = await tcb | ||
| .overrideTemplate(TestComponent, TEMPLATE) | ||
| .createAsync(TestComponent); | ||
|
|
||
| rootTC.detectChanges(); | ||
| (rootTC.debugElement.componentInstance.items as List<num>).add(3); | ||
| rootTC.detectChanges(); | ||
|
|
||
| expect(rootTC.debugElement.nativeElement.text, equals('1;2;3;')); | ||
| }); | ||
|
|
||
| group('expected failures', () { | ||
| ngTest('no type in param list', (notTyped) { | ||
| expect(1, equals(2)); | ||
| }); | ||
|
|
||
| ngSetUp((TestService testService) { | ||
| testService.init(); | ||
| }); | ||
|
|
||
| // This would fail, since setUpProviders is used after a call to ngSetUp has already | ||
| // initialized the injector. | ||
| group('nested', () { | ||
| setUpProviders(() => [TestService]); | ||
|
|
||
| test('foo', () { | ||
| expect(1 + 1, equals(2)); | ||
| }); | ||
| }); | ||
| }, skip: 'expected failures'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this create a new temp dir on every executed
gulpcommand? Maybe it should be moved into a subtask oftest.dart.angular2_testing/ci?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it is pretty terrible. I split into two subtasks so that at least the sane one can be run by itself.