|
| 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) |
0 commit comments