#resource/technology
Use `fakeAsync` & `waitForAsync`
## Potential Problems
- expects not running
- timeouts
```ts
describe("Async", () => {
it("should use window.setTimeout", ()=> {
let a = 1;
window.setTimeout(() => (a = a + 1));
expect(a).toBe(2);
})
});
```
-> problems with async, test already exited when the code is executed (test is passed, but the expect is not executed)
- every time when you are writing tests make sure that the test fails (with expected reason)
- to ensure that the async works, and than fix it
- to ensure that the assertions are called: beforeEach(() => expect.hasAssertions())
- don't rely on it 100%, there could be also exceptions
- use fake timers ([[Angular]] testing tools (not Jest fake timers))
```ts
it("should use Promise", () => {
let a = 1;
Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(1);
}));
});
```
- failed, but not with expected reason,
- something with micro vs macro task
- there is difference between window.setTimeout and the Promise.
- with Promise there is still problem with asynchronity
## How to deal with that?
### Native Approaches
- `done` callback
- return `Promise`
- `expect().resolves`
- `async`/`await`
#### Done Callback
```ts
it("should use the done callback", (done: DoneCallback) => {
let a = 1;
Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(1);
done();
});
});
```
- test will not stop until done function is called (but only when expect is passed)
- but done callback is also not good because error message are not telling us what we expect - we run into timeout
- expect fails and runs into timeout, done function is not called
- you have to wait for 5 seconds -> tests are slow then
```ts
it("should use the done callback", (done: DoneCallback) => {
let a = 1;
Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(1);
})
.then(done, done);
});
```
- for Promise, not for the window.setTimeout
- one older solution
#### Return Promise
```ts
it("should return Promise", () => {
let a = 1;
return Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(2);
});
});
```
#### expect(promise).resolves
```ts
it("should return Promise", () => {
let a = 1;
const promise = Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(1);
});
return expect(promise).resolves.toBe(2);
});
```
#### async / await
- modern solution
```ts
it("should use async/await", async () => {
let a = 1;
const promise = Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(1);
});
expect(await promise).toBe(2);
});
```
- function returns promise that resolves nothing
### Angular-based Approaches
two important functions provided by Angular:
- waitForAsync
- fakeAsync
- flushMicrotasks: run all Microtasks
- tick: move forward in time
- flush: run all asynchronous tasks
#### waitForAsync
```ts
it("should use waitForAsync",
waitForAsync(() => {
let a = 1;
Promise.resolve().then(() => {
a = a + 1;
expect(a).toBe(1);
});
});
}));
```
- problem with `waitForAsync` or `fakeAsync`
- Angular has access to all the threads that were started, and waits for everything (micro and macro tasks)
- window.setX methods are macro tasks
- test that use window.setTimeout test that interacts with this one
- this is the nightmare!
- we should not allow tests to influence each other -> fix window.setTimeout by adding waitForAsync
- if still errors, fix it with:
- Go to `package.json`
- add another parameter `--detect-open-handlers` for `test:watch` script
- however, tests become slower (that's why it is only in `--watch` method, not for everything)
- however, disadvantage is that you can see the strange error message, with `--watch` you can see the real error message and fix it
- `waitForAsync` cannot be applied always, that's why we have also `fakeAsync`
#### fakeAsync
```ts
it("should use fakeAsync",
fakeAsync(() => {
let a = 1;
window.setTimeout(() => {
a = a + 1;
expect(a).toBe(2);
});
});
}));
```
- behave little bit differently,
- inside the test macro task is defined, but not executed
- execute with `tick()`
```ts
it("should use fakeAsync", fakeAsync(() => {
let a = 1;
window.setTimeout(() => {
a = a + 1;
});
tick();
expect(a).toBe(2);
});
}));
```
- `flushMicrotasks()` - runs only micro tasks
When to use `waitForAsync` vs `fakeAsync`?
- execution times are very similar
- `fakeAsync` is generally preferred solution to decide when to trigger async and you can always have the `expect` as the last command
- `fakeAsync` will give us to have a possibility of AAA (arrange, act, assert) structure in the tests
- run all tasks immediately -> `flush()`
- however, in case that there are some restarts of threads when finished, it won't work - destroy needs to be called explicitly (some 3rd party libraries do the restarts)
### Observables
- Problem with Observables
- combined with [[Jest]],
- not with Jasmine -> Jasmine uses done callback inside of subscription (waits for timeout -> longer tests)
~~~ts
it("should use Observables", () => {
let a = 1;
of(1).subscribe(n => a = a + n);
expect(a).toBe(1);
})
~~~
- pitfall: `of` uses observables synchronously
- it depends on the way how the values are emitted
- subscribe also runs synchronously
- even with synchronous Observables we have a potential for asynchronicity -> use `fakeAsync` (or `waitForAsync`)
- don't map observable to promise with `of(1).toPromise()` (deprecated RxJs 7) use `await firstValueFrom(of(1))`, `await lastValueFrom(of(1))` instead
- because observable could not always have a return value (if returned immediately) (promise always needs one)