diff --git a/aio/content/examples/testing/src/app/demo/async-helper.spec.ts b/aio/content/examples/testing/src/app/demo/async-helper.spec.ts index 9bf42ed33f..9c9c3cb7d8 100644 --- a/aio/content/examples/testing/src/app/demo/async-helper.spec.ts +++ b/aio/content/examples/testing/src/app/demo/async-helper.spec.ts @@ -1,8 +1,7 @@ // tslint:disable-next-line:no-unused-variable import { async, fakeAsync, tick } from '@angular/core/testing'; - -import { of } from 'rxjs'; -import { delay } from 'rxjs/operators'; +import { interval, of } from 'rxjs'; +import { delay, take } from 'rxjs/operators'; describe('Angular async helper', () => { let actuallyDone = false; @@ -21,49 +20,120 @@ describe('Angular async helper', () => { }); it('should run async test with task', - async(() => { setTimeout(() => { actuallyDone = true; }, 0); })); + async(() => { setTimeout(() => { actuallyDone = true; }, 0); })); + + it('should run async test with task', async(() => { + const id = setInterval(() => { + actuallyDone = true; + clearInterval(id); + }, 100); + })); it('should run async test with successful promise', async(() => { - const p = new Promise(resolve => { setTimeout(resolve, 10); }); - p.then(() => { actuallyDone = true; }); - })); + const p = new Promise(resolve => { setTimeout(resolve, 10); }); + p.then(() => { actuallyDone = true; }); + })); it('should run async test with failed promise', async(() => { - const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); - p.catch(() => { actuallyDone = true; }); - })); + const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); + p.catch(() => { actuallyDone = true; }); + })); - // Use done. Cannot use setInterval with async or fakeAsync - // See https://github.com/angular/angular/issues/10127 + // Use done. Can also use async or fakeAsync. it('should run async test with successful delayed Observable', (done: DoneFn) => { - const source = of(true).pipe(delay(10)); - source.subscribe( - val => actuallyDone = true, - err => fail(err), - done - ); + const source = of (true).pipe(delay(10)); + source.subscribe(val => actuallyDone = true, err => fail(err), done); }); - // Cannot use setInterval from within an async zone test - // See https://github.com/angular/angular/issues/10127 - // xit('should run async test with successful delayed Observable', async(() => { - // const source = of(true).pipe(delay(10)); - // source.subscribe( - // val => actuallyDone = true, - // err => fail(err) - // ); - // })); + // #docregion fake-async-test-tick + it('should run timeout callback with delay after call tick with millis', fakeAsync(() => { + let called = false; + setTimeout(() => { called = true; }, 100); + tick(100); + expect(called).toBe(true); + })); + // #enddocregion fake-async-test-tick - // // Fail message: Error: 1 periodic timer(s) still in the queue - // // See https://github.com/angular/angular/issues/10127 - // xit('should run async test with successful delayed Observable', fakeAsync(() => { - // const source = of(true).pipe(delay(10)); - // source.subscribe( - // val => actuallyDone = true, - // err => fail(err) - // ); + // #docregion fake-async-test-date + it('should get Date diff correctly in fakeAsync', fakeAsync(() => { + const start = Date.now(); + tick(100); + const end = Date.now(); + expect(end - start).toBe(100); + })); + // #enddocregion fake-async-test-date - // tick(); - // })); + // #docregion fake-async-test-rxjs + it('should get Date diff correctly in fakeAsync with rxjs scheduler', fakeAsync(() => { + // need to add `import 'zone.js/dist/zone-patch-rxjs-fake-async' + // to patch rxjs scheduler + let result = null; + of ('hello').pipe(delay(1000)).subscribe(v => { result = v; }); + expect(result).toBeNull(); + tick(1000); + expect(result).toBe('hello'); + + const start = new Date().getTime(); + let dateDiff = 0; + interval(1000).pipe(take(2)).subscribe(() => dateDiff = (new Date().getTime() - start)); + + tick(1000); + expect(dateDiff).toBe(1000); + tick(1000); + expect(dateDiff).toBe(2000); + })); + // #enddocregion fake-async-test-rxjs + + // #docregion fake-async-test-clock + describe('use jasmine.clock()', () => { + // need to config __zone_symbol__fakeAsyncPatchLock flag + // before loading zone.js/dist/zone-testing + beforeEach(() => { jasmine.clock().install(); }); + afterEach(() => { jasmine.clock().uninstall(); }); + it('should auto enter fakeAsync', () => { + // is in fakeAsync now, don't need to call fakeAsync(testFn) + let called = false; + setTimeout(() => { called = true; }, 100); + jasmine.clock().tick(100); + expect(called).toBe(true); + }); + }); + // #enddocregion fake-async-test-clock + + // #docregion async-test-promise-then + describe('test jsonp', () => { + function jsonp(url: string, callback: Function) { + // do a jsonp call which is not zone aware + } + // need to config __zone_symbol__supportWaitUnResolvedChainedPromise flag + // before loading zone.js/dist/zone-testing + it('should wait until promise.then is called', async(() => { + let finished = false; + new Promise((res, rej) => { + jsonp('localhost:8080/jsonp', () => { + // success callback and resolve the promise + finished = true; + res(); + }); + }).then(() => { + // async will wait until promise.then is called + // if __zone_symbol__supportWaitUnResolvedChainedPromise is set + expect(finished).toBe(true); + }); + })); + }); + // #enddocregion async-test-promise-then + + it('should run async test with successful delayed Observable', async(() => { + const source = of (true).pipe(delay(10)); + source.subscribe(val => actuallyDone = true, err => fail(err)); + })); + + it('should run async test with successful delayed Observable', fakeAsync(() => { + const source = of (true).pipe(delay(10)); + source.subscribe(val => actuallyDone = true, err => fail(err)); + + tick(10); + })); }); diff --git a/aio/content/guide/setup.md b/aio/content/guide/setup.md index 884797ce80..5898c6dfa0 100644 --- a/aio/content/guide/setup.md +++ b/aio/content/guide/setup.md @@ -356,3 +356,24 @@ If you develop angular locally with `ng serve`, there will be `websocket` connec In windows, by default one application can only have 6 websocket connections, MSDN WebSocket Settings. So if IE was refreshed manunally or automatically by `ng serve`, sometimes, the websocket will not close properly, when websocket connections exceed limitations, `SecurityError` will be thrown, this error will not affect the angular application, you can just restart IE to clear this error, or modify the windows registry to update the limitations. + +## Appendix: test using `fakeAsync()/async()` + +If you use the `fakeAsync()/async()` helper function to run unit tests (for details, read [testing guide](guide/testing#async-test-with-fakeasync)), you need to import `zone.js/dist/zone-testing` in your test setup file. + +
+If you create project with `Angular/CLI`, it is already imported in `src/test.ts`. +
+ +And in the earlier versions of `Angular`, the following files were imported or added in your html file: + +``` +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +``` + +You can still load those files separately, but the order is important, you must import `proxy` before `sync-test`, `async-test`, `fake-async-test` and `jasmine-patch`. And you also need to import `sync-test` before `jasmine-patch`, so it is recommended to just import `zone-testing` instead of loading those separated files. \ No newline at end of file diff --git a/aio/content/guide/testing.md b/aio/content/guide/testing.md index 9116e24d49..e17e94db6f 100644 --- a/aio/content/guide/testing.md +++ b/aio/content/guide/testing.md @@ -1054,6 +1054,8 @@ value becomes available. The test must become _asynchronous_. #### Async test with _fakeAsync()_ +To use `fakeAsync()` functionality, you need to import `zone-testing`, for details, please read [setup guide](guide/setup#appendix-test-using-fakeasyncasync). + The following test confirms the expected behavior when the service returns an `ErrorObservable`. { /* test body */ })` ``` -The `fakeAsync` function enables a linear coding style by running the test body in a special _fakeAsync test zone_. +The `fakeAsync()` function enables a linear coding style by running the test body in a special `fakeAsync test zone`. The test body appears to be synchronous. There is no nested syntax (like a `Promise.then()`) to disrupt the flow of control. @@ -1080,12 +1082,55 @@ You do have to call `tick()` to advance the (virtual) clock. Calling `tick()` simulates the passage of time until all pending asynchronous activities finish. In this case, it waits for the error handler's `setTimeout()`; -The `tick` function is one of the Angular testing utilities that you import with `TestBed`. -It's a companion to `fakeAsync` and you can only call it within a `fakeAsync` body. +The `tick()` function accepts milliseconds as parameter (defaults to 0 if not provided). The parameter represents how much the virtual clock advances. For example, if you have a `setTimeout(fn, 100)` in a `fakeAsync()` test, you need to use tick(100) to trigger the fn callback. + + + + +The `tick()` function is one of the Angular testing utilities that you import with `TestBed`. +It's a companion to `fakeAsync()` and you can only call it within a `fakeAsync()` body. + +#### Comparing dates inside fakeAsync() + +`fakeAsync()` simulates passage of time, which allows you to calculate the difference between dates inside `fakeAsync()`. + + + + +#### jasmine.clock with fakeAsync() + +Jasmine also provides a `clock` feature to mock dates. Angular automatically runs tests that are run after +`jasmine.clock().install()` is called inside a `fakeAsync()` method until `jasmine.clock().uninstall()` is called. `fakeAsync()` is not needed and throws an error if nested. + +By default, this feature is disabled. To enable it, set a global flag before import `zone-testing`. + +If you use the Angular CLI, configure this flag in `src/test.ts`. + +``` +(window as any)['__zone_symbol__fakeAsyncPatchLock'] = true; +import 'zone.js/dist/zone-testing'; +``` + + + + +#### Using the RxJS scheduler inside fakeAsync() + +You can also use RxJS scheduler in `fakeAsync()` just like using `setTimeout()` or `setInterval()`, but you need to import `zone.js/dist/zone-patch-rxjs-fake-async` to patch RxJS scheduler. + + #### Support more macroTasks -By default `fakeAsync` supports the following `macroTasks`. +By default `fakeAsync()` supports the following `macroTasks`. - setTimeout - setInterval @@ -1106,7 +1151,7 @@ If you run other `macroTask` such as `HTMLCanvasElement.toBlob()`, `Unknown macr -If you want to support such case, you need to define the `macroTask` you want to support in `beforeEach`. +If you want to support such case, you need to define the `macroTask` you want to support in `beforeEach()`. For example: ```javascript @@ -1203,6 +1248,8 @@ Then you can assert that the quote element displays the expected text. #### Async test with _async()_ +To use `async()` functionality, you need to import `zone-testing`, for details, please read [setup guide](guide/setup#appendix-test-using-fakeasyncasync). + The `fakeAsync()` utility function has a few limitations. In particular, it won't work if the test body makes an `XHR` call. @@ -1226,12 +1273,13 @@ Here's the previous `fakeAsync()` test, re-written with the `async()` utility. The `async()` utility hides some asynchronous boilerplate by arranging for the tester's code to run in a special _async test zone_. -You don't have to pass Jasmine's `done()` into the test and call `done()` -in promise or observable callbacks. +You don't need to pass Jasmine's `done()` into the test and call `done()` because it is `undefined` in promise or observable callbacks. But the test's asynchronous nature is revealed by the call to `fixture.whenStable()`, which breaks the linear flow of control. +When using an `intervalTimer()` such as `setInterval()` in `async()`, remember to cancel the timer with `clearInterval()` after the test, otherwise the `async()` never ends. + {@a when-stable} #### _whenStable_ @@ -1250,18 +1298,19 @@ update the quote element with the expected text. #### Jasmine _done()_ -While the `async` and `fakeAsync` functions greatly +While the `async()` and `fakeAsync()` functions greatly simplify Angular asynchronous testing, you can still fall back to the traditional technique and pass `it` a function that takes a [`done` callback](http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support). +You can't call `done()` in `async()` or `fakeAsync()` functions, because the `done parameter` +is `undefined`. + Now you are responsible for chaining promises, handling errors, and calling `done()` at the appropriate moments. -Writing test functions with `done()`, is more cumbersome than `async`and `fakeAsync`. -But it is occasionally necessary. -For example, you can't call `async` or `fakeAsync` when testing -code that involves the `intervalTimer()` or the RxJS `delay()` operator. +Writing test functions with `done()`, is more cumbersome than `async()`and `fakeAsync()`. +But it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`. Here are two more versions of the previous test, written with `done()`. The first one subscribes to the `Observable` exposed to the template by the component's `quote` property. @@ -2124,7 +2173,6 @@ Here are a few more `HeroDetailComponent` tests to reinforce the point. {@a compile-components} ### Calling _compileComponents()_ -
You can ignore this section if you _only_ run tests with the CLI `ng test` command @@ -2688,7 +2736,7 @@ Here's a summary of the stand-alone functions, in order of likely utility: - When a `fakeAsync` test ends with pending timer event _tasks_ (queued `setTimeOut` and `setInterval` callbacks), + When a `fakeAsync()` test ends with pending timer event _tasks_ (queued `setTimeOut` and `setInterval` callbacks), the test fails with a clear error message. In general, a test should end with no queued tasks. @@ -2705,7 +2753,7 @@ Here's a summary of the stand-alone functions, in order of likely utility: - When a `fakeAsync` test ends with pending _micro-tasks_ such as unresolved promises, + When a `fakeAsync()` test ends with pending _micro-tasks_ such as unresolved promises, the test fails with a clear error message. In general, a test should wait for micro-tasks to finish.