docs: add fakeAsync test new feature document (#23117)

PR Close #23117
This commit is contained in:
JiaLi.Passion 2018-04-01 23:09:00 +09:00 committed by Jason Aden
parent 6c6bc95ac0
commit ccceff5ecc
3 changed files with 191 additions and 52 deletions

View File

@ -1,8 +1,7 @@
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
import { async, fakeAsync, tick } from '@angular/core/testing'; import { async, fakeAsync, tick } from '@angular/core/testing';
import { interval, of } from 'rxjs';
import { of } from 'rxjs'; import { delay, take } from 'rxjs/operators';
import { delay } from 'rxjs/operators';
describe('Angular async helper', () => { describe('Angular async helper', () => {
let actuallyDone = false; let actuallyDone = false;
@ -21,49 +20,120 @@ describe('Angular async helper', () => {
}); });
it('should run async test with task', 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(() => { it('should run async test with successful promise', async(() => {
const p = new Promise(resolve => { setTimeout(resolve, 10); }); const p = new Promise(resolve => { setTimeout(resolve, 10); });
p.then(() => { actuallyDone = true; }); p.then(() => { actuallyDone = true; });
})); }));
it('should run async test with failed promise', async(() => { it('should run async test with failed promise', async(() => {
const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); const p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
p.catch(() => { actuallyDone = true; }); p.catch(() => { actuallyDone = true; });
})); }));
// Use done. Cannot use setInterval with async or fakeAsync // Use done. Can also use async or fakeAsync.
// See https://github.com/angular/angular/issues/10127
it('should run async test with successful delayed Observable', (done: DoneFn) => { it('should run async test with successful delayed Observable', (done: DoneFn) => {
const source = of(true).pipe(delay(10)); const source = of (true).pipe(delay(10));
source.subscribe( source.subscribe(val => actuallyDone = true, err => fail(err), done);
val => actuallyDone = true,
err => fail(err),
done
);
}); });
// Cannot use setInterval from within an async zone test // #docregion fake-async-test-tick
// See https://github.com/angular/angular/issues/10127 it('should run timeout callback with delay after call tick with millis', fakeAsync(() => {
// xit('should run async test with successful delayed Observable', async(() => { let called = false;
// const source = of(true).pipe(delay(10)); setTimeout(() => { called = true; }, 100);
// source.subscribe( tick(100);
// val => actuallyDone = true, expect(called).toBe(true);
// err => fail(err) }));
// ); // #enddocregion fake-async-test-tick
// }));
// // Fail message: Error: 1 periodic timer(s) still in the queue // #docregion fake-async-test-date
// // See https://github.com/angular/angular/issues/10127 it('should get Date diff correctly in fakeAsync', fakeAsync(() => {
// xit('should run async test with successful delayed Observable', fakeAsync(() => { const start = Date.now();
// const source = of(true).pipe(delay(10)); tick(100);
// source.subscribe( const end = Date.now();
// val => actuallyDone = true, expect(end - start).toBe(100);
// err => fail(err) }));
// ); // #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);
}));
}); });

View File

@ -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, <a href="https://msdn.microsoft.com/library/ee330736%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#websocket_maxconn" title="MSDN WebSocket settings">MSDN WebSocket Settings</a>. In windows, by default one application can only have 6 websocket connections, <a href="https://msdn.microsoft.com/library/ee330736%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396#websocket_maxconn" title="MSDN WebSocket settings">MSDN WebSocket Settings</a>.
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. 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.
<div class="alert is-important">
If you create project with `Angular/CLI`, it is already imported in `src/test.ts`.
</div>
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.

View File

@ -1237,6 +1237,8 @@ value becomes available. The test must become _asynchronous_.
#### Async test with _fakeAsync()_ #### 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`. The following test confirms the expected behavior when the service returns an `ErrorObservable`.
<code-example <code-example
@ -1250,7 +1252,7 @@ Note that the `it()` function receives an argument of the following form.
fakeAsync(() => { /* test body */ })` fakeAsync(() => { /* 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. The test body appears to be synchronous.
There is no nested syntax (like a `Promise.then()`) to disrupt the flow of control. There is no nested syntax (like a `Promise.then()`) to disrupt the flow of control.
@ -1263,12 +1265,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. Calling `tick()` simulates the passage of time until all pending asynchronous activities finish.
In this case, it waits for the error handler's `setTimeout()`; 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`. 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.
It's a companion to `fakeAsync` and you can only call it within a `fakeAsync` body.
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
region="fake-async-test-tick">
</code-example>
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()`.
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
region="fake-async-test-date">
</code-example>
#### 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';
```
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
region="fake-async-test-clock">
</code-example>
#### 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.
<code-example
path="testing/src/app/demo/async-helper.spec.ts"
region="fake-async-test-rxjs">
</code-example>
#### Support more macroTasks #### Support more macroTasks
By default `fakeAsync` supports the following `macroTasks`. By default `fakeAsync()` supports the following `macroTasks`.
- setTimeout - setTimeout
- setInterval - setInterval
@ -1289,7 +1334,7 @@ If you run other `macroTask` such as `HTMLCanvasElement.toBlob()`, `Unknown macr
</code-pane> </code-pane>
</code-tabs> </code-tabs>
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: For example:
```javascript ```javascript
@ -1386,6 +1431,8 @@ Then you can assert that the quote element displays the expected text.
#### Async test with _async()_ #### 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. The `fakeAsync()` utility function has a few limitations.
In particular, it won't work if the test body makes an `XHR` call. In particular, it won't work if the test body makes an `XHR` call.
@ -1409,12 +1456,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 The `async()` utility hides some asynchronous boilerplate by arranging for the tester's code
to run in a special _async test zone_. to run in a special _async test zone_.
You don't have to pass Jasmine's `done()` into the test and call `done()` You don't need to pass Jasmine's `done()` into the test and call `done()` because it is `undefined` in promise or observable callbacks.
in promise or observable callbacks.
But the test's asynchronous nature is revealed by the call to `fixture.whenStable()`, But the test's asynchronous nature is revealed by the call to `fixture.whenStable()`,
which breaks the linear flow of control. 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} {@a when-stable}
#### _whenStable_ #### _whenStable_
@ -1433,18 +1481,19 @@ update the quote element with the expected text.
#### Jasmine _done()_ #### Jasmine _done()_
While the `async` and `fakeAsync` functions greatly While the `async()` and `fakeAsync()` functions greatly
simplify Angular asynchronous testing, simplify Angular asynchronous testing,
you can still fall back to the traditional technique you can still fall back to the traditional technique
and pass `it` a function that takes a and pass `it` a function that takes a
[`done` callback](http://jasmine.github.io/2.0/introduction.html#section-Asynchronous_Support). [`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. 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`. Writing test functions with `done()`, is more cumbersome than `async()`and `fakeAsync()`.
But it is occasionally necessary. But it is occasionally necessary when code involves the `intervalTimer()` like `setInterval`.
For example, you can't call `async` or `fakeAsync` when testing
code that involves the `intervalTimer()` or the RxJS `delay()` operator.
Here are two more versions of the previous test, written with `done()`. 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. The first one subscribes to the `Observable` exposed to the template by the component's `quote` property.
@ -2307,7 +2356,6 @@ Here are a few more `HeroDetailComponent` tests to reinforce the point.
{@a compile-components} {@a compile-components}
### Calling _compileComponents()_ ### Calling _compileComponents()_
<div class="alert is-helpful"> <div class="alert is-helpful">
You can ignore this section if you _only_ run tests with the CLI `ng test` command You can ignore this section if you _only_ run tests with the CLI `ng test` command
@ -2871,7 +2919,7 @@ Here's a summary of the stand-alone functions, in order of likely utility:
<td> <td>
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. the test fails with a clear error message.
In general, a test should end with no queued tasks. In general, a test should end with no queued tasks.
@ -2888,7 +2936,7 @@ Here's a summary of the stand-alone functions, in order of likely utility:
<td> <td>
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. the test fails with a clear error message.
In general, a test should wait for micro-tasks to finish. In general, a test should wait for micro-tasks to finish.