Skip to content

Commit 35bc434

Browse files
committed
feat(faketime): added fakeTime wrapper function
fakeTime allows you to test async Observables using the VirtualTimeScheduler behind the scenes
1 parent 6cb3f09 commit 35bc434

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed

README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,106 @@ it('should spy on Observable errors', () => {
9595
expect(observerSpy.getError()).toEqual('FAKE ERROR');
9696
});
9797
```
98+
99+
# Testing Async Observables
100+
101+
### ▶ For _Angular_ code - just use `fakeAsync`
102+
103+
Example:
104+
105+
```js
106+
it('should test Angular code with delay', fakeAsync(() => {
107+
const observerSpy = new ObserverSpy();
108+
109+
const fakeObservable = of('fake value').pipe(delay(1000));
110+
111+
const sub = fakeObservable.subscribe(observerSpy);
112+
113+
tick(1000);
114+
115+
sub.unsubscribe();
116+
117+
expect(observerSpy.getLastValue()).toEqual('fake value');
118+
}));
119+
```
120+
121+
### ▶ For only _promises_ (no timeouts / intervals) - just use `done`
122+
123+
You don't even need to use an observer spy
124+
125+
Example:
126+
127+
```js
128+
it('should work with promises', (done) => {
129+
const observerSpy: ObserverSpy<string> = new ObserverSpy();
130+
131+
const fakeService = {
132+
getData() {
133+
return Promise.resolve('fake data');
134+
},
135+
};
136+
const fakeObservable = of('').pipe(switchMap(() => fakeService.getData()));
137+
138+
fakeObservable.subscribe(observerSpy);
139+
140+
observerSpy.onComplete(() => {
141+
expect(observerSpy.getLastValue()).toEqual('fake data');
142+
done();
143+
});
144+
});
145+
```
146+
147+
### ▶ For _time based_ rxjs code (timeouts / intervals / animations) - use `fakeTime`
148+
149+
`fakeTime` is a utility function that wraps the test callback.
150+
151+
It does the following things:
152+
153+
1. Changes the `AsyncScheduler` delegate to use `VirtualTimeScheduler` (which gives you the ability to use "virtual time" instead of having long tests.
154+
2. Passes a `flush` function you can call to `flush()` when you want to virtually pass time forward.
155+
3. Works well with `done` if you pass it as the second parameter (instead of the first)
156+
157+
Example:
158+
159+
```js
160+
it(
161+
'should handle delays with a virtual scheduler',
162+
fakeTime((flush) => {
163+
const VALUES = ['first', 'second', 'third'];
164+
const observerSpy: ObserverSpy<string> = new ObserverSpy();
165+
const delayedObservable: Observable<string> = of(...VALUES).pipe(delay(20000));
166+
167+
const sub = delayedObservable.subscribe(observerSpy);
168+
flush();
169+
sub.unsubscribe();
170+
171+
expect(observerSpy.getValues()).toEqual(VALUES);
172+
})
173+
);
174+
175+
it(
176+
'should handle be able to deal with done functionality as well',
177+
fakeTime((flush, done) => {
178+
const VALUES = ['first', 'second', 'third'];
179+
const observerSpy: ObserverSpy<string> = new ObserverSpy();
180+
const delayedObservable: Observable<string> = of(...VALUES).pipe(delay(20000));
181+
182+
const sub = delayedObservable.subscribe(observerSpy);
183+
flush();
184+
sub.unsubscribe();
185+
186+
observerSpy.onComplete(() => {
187+
expect(observerSpy.getValues()).toEqual(VALUES);
188+
done();
189+
});
190+
})
191+
);
192+
```
193+
194+
### ▶ For _ajax_ calls (http) - they shouldn't be tested in a unit / micro test... 😜
195+
196+
Yeah. Test those in an integration test
197+
198+
## Wanna learn more?
199+
200+
In my [class testing In action course](http://testangular.com/?utm_source=github&utm_medium=link&utm_campaign=observer-spy) I go over all the differences and show you how to use this library to test stuff like `switchMap`, `interval` etc...
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`fakeTime should fail with no parameters 1`] = `
4+
"
5+
\\"fakeTime()\\" callback must be declared with at least one parameter
6+
For example: fakeAsync((flush)=> ...)
7+
"
8+
`;

src/fake-time.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { ObserverSpy } from './observer-spy';
2+
import { Observable, of } from 'rxjs';
3+
import { delay } from 'rxjs/operators';
4+
import { fakeTime } from './fake-time';
5+
6+
describe('fakeTime', () => {
7+
function getDelayedObservable() {
8+
const VALUES = ['first', 'second', 'third'];
9+
const observerSpy: ObserverSpy<string> = new ObserverSpy();
10+
const delayedObservable: Observable<string> = of(...VALUES).pipe(delay(20000));
11+
12+
return {
13+
VALUES,
14+
observerSpy,
15+
delayedObservable,
16+
};
17+
}
18+
19+
it('should fail with no parameters', () => {
20+
function fakeTimeWithNoParams() {
21+
fakeTime(() => {});
22+
}
23+
expect(fakeTimeWithNoParams).toThrowErrorMatchingSnapshot();
24+
});
25+
26+
it(
27+
'should handle delays with a virtual scheduler',
28+
fakeTime((flush) => {
29+
const { observerSpy, delayedObservable, VALUES } = getDelayedObservable();
30+
31+
const sub = delayedObservable.subscribe(observerSpy);
32+
flush();
33+
sub.unsubscribe();
34+
35+
expect(observerSpy.getValues()).toEqual(VALUES);
36+
})
37+
);
38+
39+
it(
40+
'should handle be able to deal with done functionality as well',
41+
fakeTime((flush, done) => {
42+
const { observerSpy, delayedObservable, VALUES } = getDelayedObservable();
43+
44+
const sub = delayedObservable.subscribe(observerSpy);
45+
flush();
46+
sub.unsubscribe();
47+
48+
observerSpy.onComplete(() => {
49+
expect(observerSpy.getValues()).toEqual(VALUES);
50+
done();
51+
});
52+
})
53+
);
54+
});

src/fake-time.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { VirtualTimeScheduler } from 'rxjs';
2+
import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler';
3+
4+
export function fakeTime(callback: (...args: any[]) => any) {
5+
if (callback.length === 0) {
6+
throw new Error(`
7+
"fakeTime()" callback must be declared with at least one parameter
8+
For example: fakeAsync((flush)=> ...)
9+
`);
10+
}
11+
12+
if (callback.length === 1) {
13+
return function () {
14+
const virtualScheduler = new VirtualTimeScheduler();
15+
AsyncScheduler.delegate = virtualScheduler;
16+
17+
function customFlush() {
18+
virtualScheduler.flush();
19+
}
20+
const originalReturnedValue = callback(customFlush);
21+
22+
AsyncScheduler.delegate = undefined;
23+
24+
return originalReturnedValue;
25+
};
26+
}
27+
28+
return function (done: () => void) {
29+
const virtualScheduler = new VirtualTimeScheduler();
30+
AsyncScheduler.delegate = virtualScheduler;
31+
32+
function customFlush() {
33+
virtualScheduler.flush();
34+
}
35+
const originalReturnedValue = callback(customFlush, done);
36+
37+
AsyncScheduler.delegate = undefined;
38+
39+
return originalReturnedValue;
40+
};
41+
}

0 commit comments

Comments
 (0)