Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit dc1a083

Browse files
committed
doc(test): add draft doc of zone-testing
1 parent ad098b0 commit dc1a083

File tree

5 files changed

+295
-19
lines changed

5 files changed

+295
-19
lines changed

doc/design/TEST.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Zone Testing
2+
3+
`zone.js` has a `zone-testing.js` bundle, which provides a lot of functionalities for testing.
4+
include:
5+
6+
1. FakeAsyncTestZoneSpec: simulate system timer to make `async` test faster and stable.
7+
2. AsyncTestZoneSpec: automatically wait for all async tasks to finish.
8+
3. SyncTestZoneSpec: force all tests to be synchronized.
9+
4. Jasmine/Mocha/Jest supports.
10+
11+
## FakeAsyncTestZoneSpec <TBD>
12+
13+
Add `fakeAsync` document later.
14+
15+
## AsyncTestZoneSpec <TBD>
16+
17+
Add `async` document later.
18+
19+
## SyncTestZoneSpec <TBD>
20+
21+
Add `sync` document later.
22+
23+
## Unify jasmine/mocha/jest
24+
25+
`zone-testing` support `jasmine` and `mocha` runner, when `zone-testing` is loaded, it will detect current test environment(jasmine or mocha) and monkey-patch current runner accordingly. For detail, please check this document [test runner](./TEST_RUNNER.md).

doc/design/TEST_RUNNER.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Test Runner
2+
3+
`zone-testing` monkey-patch `jasmine` and `mocha` runner to provide the following functionalities.
4+
5+
1. All `describe/xdescribe/fdescribe` are guaranteed to run in `SyncTestZone`, so no `async` code are allowed under `describe` directly.
6+
7+
```javascript
8+
describe('test', () => {
9+
// setTimeout(() => {}, 100); // not allowed
10+
});
11+
```
12+
13+
2. Support `fakeAsync` in `test`.
14+
15+
```javascript
16+
import 'zone.js`;
17+
import 'zone.js/dist/zone-testing`;
18+
// TODO: this is too complex to load fakeAsyncTest, should expose as global function.
19+
const {resetFakeAsyncZone, flushMicrotasks, discardPeriodicTasks, tick, flush, fakeAsync} = (Zone as any)[Zone.__symbol__('fakeAsyncTest')];
20+
21+
// support simulate timer function.
22+
describe('fakeAsync', () => {
23+
it('setTimeout', fakeAsync(() => {
24+
let called = false;
25+
setTimeout(() => {called = true}, 100);
26+
expect(called).toBe(false);
27+
tick(100);
28+
expect(called).toBe(true);
29+
}));
30+
31+
it ('Promise', fakeAsync(() => {
32+
let thenRan = false;
33+
Promise.resolve(null).then((_) => {
34+
thenRan = true;
35+
});
36+
37+
expect(thenRan).toEqual(false);
38+
flushMicrotasks();
39+
expect(thenRan).toEqual(true);
40+
}));
41+
});
42+
```
43+
44+
Will add more examples later.
45+
46+
3. support `asyncTest`.
47+
48+
```javascript
49+
import 'zone.js`;
50+
import 'zone.js/dist/zone-testing`;
51+
// TODO: this is too complex to load asyncTest, should expose as global function.
52+
const asyncTest = (Zone as any)[Zone.__symbol__('asyncTest')];
53+
54+
describe('async', () => {
55+
it('setTimeout and Promise', asyncTest(() => { // don't need to provide doneFn here.
56+
let timeoutCalled = false;
57+
setTimeout(() => {
58+
timeoutCalled = true;
59+
}, 100);
60+
let thenRan = false;
61+
Promise.resolve(null).then((_) => {
62+
thenRan = true;
63+
});
64+
setTimeout(() => {
65+
expect(timeoutCalled).toBe(true);
66+
expect(thenRan).toBe(true);
67+
}, 200);
68+
}));
69+
});
70+
```
71+
72+
Will add more examples later.
73+
74+
4. Date.now/jasmine.clock()/rxjs.Scheduler support in fakeAsync.
75+
76+
```javascript
77+
describe('fakeAsync', () => {
78+
it('setTimeout', fakeAsync(() => {
79+
const start = Date.now();
80+
testZoneSpec.tick(100);
81+
const end = Date.now();
82+
expect(end - start).toBe(100);
83+
}));
84+
});
85+
86+
// NOTE: automatically fall into fakeAsync need to set this flag to true before loading zone-testing
87+
// (window as any).__zone_symbol__fakeAsyncPatchLock = true;
88+
describe('jasmine.clock', () => {
89+
beforeEach(() => {
90+
jasmine.clock().install();
91+
});
92+
93+
afterEach(() => {
94+
jasmine.clock().uninstall();
95+
});
96+
97+
it('should get date diff correctly', () => { // we don't need fakeAsync here.
98+
// automatically run into fake async zone, because jasmine.clock() is installed.
99+
const start = Date.now();
100+
jasmine.clock().tick(100);
101+
const end = Date.now();
102+
expect(end - start).toBe(100);
103+
});
104+
});
105+
106+
// import the following files to patch Date.now of rxjs.Scheduler/rxjs.asap/rxjs.async
107+
import 'zone.js/dist/zone-patch-rxjs-fakeAsync';
108+
109+
describe('fakeAsync', () => {
110+
it('should get date diff correctly', fakeAsync(() => {
111+
let result = null;
112+
const observable = new Observable((subscribe: any) => {
113+
subscribe.next('hello');
114+
});
115+
observable.delay(1000).subscribe(v => {
116+
result = v;
117+
});
118+
expect(result).toBeNull();
119+
testZoneSpec.tick(1000);
120+
expect(result).toBe('hello');
121+
});
122+
});
123+
```
124+
125+
5. Unify `jasmine/mocha/jest` test cases in `Mocha` runner.
126+
127+
You can write `jasmine`, `mocha`, `jest` style test cases when you use `Mocha` runner.
128+
You can use `jasmine spy` inside `mocha` cases, you can also use `jest style expect and mock`.
129+
130+
```javascript
131+
describe('mixed', () => {
132+
// jasmine style beforeAll
133+
beforeAll(() => {});
134+
135+
// mocha style setup
136+
setup(() => {});
137+
138+
it('jasmine style', () => {
139+
expect(true).toBe(true);
140+
});
141+
142+
// mocha specify
143+
specify('mocha style', () => {
144+
foo = {
145+
setBar: function(value: any) {
146+
bar = value;
147+
}
148+
};
149+
150+
spyOn(foo, 'setBar').and.callThrough();
151+
foo.setBar(123);
152+
expect(bar).toEqual(123);
153+
});
154+
155+
test('jest style', () => {
156+
// TODO: will add type definition later.
157+
(expect([1, 2, 3]) as any).toHaveLength(3);
158+
// can handle promise with jest expect.resolves
159+
return (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon');
160+
});
161+
162+
test('jest mock', () => {
163+
// can support jest.mock
164+
const myMockFn = jest.fn(() => 'default')
165+
.mockImplementationOnce(() => 'first call')
166+
.mockImplementationOnce(() => 'second call');
167+
168+
// 'first call', 'second call', 'default', 'default'
169+
logs.push(myMockFn(), myMockFn(), myMockFn(), myMockFn());
170+
expect(logs).toEqual(['first call', 'second call', 'default', 'default']);
171+
});
172+
});
173+
```
174+
175+
For full examples, you can find in [jasmine](../../test/spec/mocha/jasmine-bridge.spec.ts) and [jest](../../test/spec/jest-bridge.spec.ts)
176+
177+
And here is the mapping about which `jasmine/jest` functionalities are supported inside `Mocha` runner.
178+
179+
1. BDD/TDD interface.
180+
181+
| jasmine | mocha | jest |
182+
| --- | --- | --- |
183+
| beforeAll | before | beforeAll |
184+
| afterAll | after | beforeAll |
185+
| xdescribe | describe.skip | describe.skip |
186+
| fdescribe | describe.only | describe.only |
187+
| xit | it.skip | it.skip |
188+
| fit | it.only | it.only |
189+
190+
And of course you can use `setup/suiteSetup/tearDown/suiteTearDown` in Mocha.
191+
192+
2. jasmine.clock
193+
You can use `jasmine.clock` inside `Mocha` runner. And you can also use the `auto fakeAsync` feature.
194+
195+
3. jasmine.spy
196+
In Mocha, no built-in spy lib is provided, you can still use 3rd party spy library, with `zone-testing`, you can use `jasmine spy`, you can use `jasmine.createSpy, jasmine.createSpyObj, spyOn, spyOnProperty` to create `jasmine style spy`. And you can also use all `spy strategy` such as `callThough/callFake/...`
197+
198+
4. jest mock
199+
Not only `jasmine spy`, you can also use `jest mock`, You can use `jest.fn` to create `jest style mock`. And you can also use the `jest mockFn method` such as `mochFn.mockReturnValue`.
200+
201+
5. jasmine expect
202+
In Mocha, there is no built in `expect` lib, you can still use 3rd party `assert/expect` lib, with `zone-testing`, you can use `jasmine expect mathers`.
203+
204+
- nothing
205+
- toBe
206+
- toBeCloseTo
207+
- toEqual
208+
- toBeGreaterThan
209+
- toBeGreaterThanOrEqual
210+
- toBeLessThan
211+
- toBeLessThanOrEqual
212+
- toBeDefined
213+
- toBeNaN
214+
- toBeNegativeInfinity
215+
- toBeNull
216+
- toBePositiveInfinity
217+
- toBeUndefined
218+
- toThrow
219+
- toThrowError
220+
- toBeTruthy
221+
- toBeFalsy
222+
- toContain
223+
- toHaveBeenCalled
224+
- toHaveBeenCalledWith
225+
- toMatch
226+
- not
227+
228+
You can also add customMatchers and customEqualityTesters.
229+
230+
6. Jest mock
231+
And you can also get `jest expect matchers`.
232+
233+
- toBeCalled
234+
- toBeCalledWith
235+
- toHaveBeenCalledTimes
236+
- lastCalledWith
237+
- toHaveBeenLastCalledWith
238+
- toBeInstanceOf
239+
- toContainEqual
240+
- toHaveLength
241+
- toHaveProperty
242+
- toMatchObject
243+
244+
And `expect util method`.
245+
246+
- expect.anything
247+
- expect.any
248+
- expect.arrayContaining
249+
- expect.objectContaining
250+
- expect.stringContaining
251+
- expect.stringMatching
252+
- expect.extend
253+
- expect.assertions
254+
- expect.hasAssertions
255+
- expect.resolves (Promise)
256+
- expect.rejects (Promise)

lib/mocha/jasmine-bridge/jasmine.bdd.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ export function mappingBDD(jasmine: any, Mocha: any, global: any) {
4747
global['fail'] = function(error?: any) {
4848
const err = error ? error : new Error();
4949
throw err;
50-
}
50+
};
5151
}
5252
}

lib/mocha/jasmine-bridge/jasmine.expect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ function buildResolveRejects(key: string, matchers: any, expected: any, isNot =
344344
(error: any) => {
345345
throw error;
346346
});
347-
}
347+
};
348348
};
349349
if (isNot) {
350350
matchers.resolves.not[key] = resolveFnFactory(true);
@@ -360,7 +360,7 @@ function buildResolveRejects(key: string, matchers: any, expected: any, isNot =
360360
return isNot ? newMatchers.not[key].apply(self, args) :
361361
newMatchers[key].apply(self, args);
362362
});
363-
}
363+
};
364364
};
365365
if (isNot) {
366366
matchers.rejects.not[key] = rejectFnFactory(true);

lib/mocha/jest-bridge/jest.expect.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,37 +49,33 @@ export function expandExpect(global: any) {
4949
constructor(public expectedArray: any[]) {}
5050
}
5151

52-
expect.arrayContaining =
53-
function(expectedArray: string[]) {
52+
expect.arrayContaining = function(expectedArray: string[]) {
5453
return new ArrayContaining(expectedArray);
55-
}
54+
};
5655

5756
class ObjectContaining {
5857
constructor(public expectedObject: any) {}
5958
}
6059

61-
expect.objectContaining =
62-
function(expectedObject: any) {
60+
expect.objectContaining = function(expectedObject: any) {
6361
return new ObjectContaining(expectedObject);
64-
}
62+
};
6563

6664
class StringContaining {
6765
constructor(public expectedString: string) {}
6866
}
6967

70-
expect.stringContaining =
71-
function(expectedString: string) {
68+
expect.stringContaining = function(expectedString: string) {
7269
return new StringContaining(expectedString);
73-
}
70+
};
7471

7572
class StringMatching {
7673
constructor(public expectedMatcher: RegExp|string) {}
7774
}
7875

79-
expect.stringMatching =
80-
function(expectedMatcher: RegExp|string) {
76+
expect.stringMatching = function(expectedMatcher: RegExp|string) {
8177
return new StringMatching(expectedMatcher);
82-
}
78+
};
8379

8480
const assertions: {test: any, numbers: number}[] = (expect as any).__zone_symbol__assertionsMap =
8581
[];
@@ -150,7 +146,7 @@ export function expandExpect(global: any) {
150146
return matcher(actual, expected);
151147
}
152148
};
153-
}
149+
};
154150
}
155151
});
156152
jasmine.addMatchers(jasmineMatchers);
@@ -258,11 +254,10 @@ export function expandExpect(global: any) {
258254
assertions.push({test: currentTest, numbers});
259255
};
260256

261-
expect.hasAssertions =
262-
function() {
257+
expect.hasAssertions = function() {
263258
const currentTest = global.Mocha.__zone_symbol__test;
264259
assertions.push({test: currentTest, numbers: 1});
265-
}
260+
};
266261

267262
if (!global.Mocha.__zone_symbol__afterEach) {
268263
global.Mocha.__zone_symbol__afterEach = [];

0 commit comments

Comments
 (0)