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

WIP(test): unify mocha/jasmine/jest test #1084

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ parsed-idl/
.vscode
npm-debug.log
build-esm/
yarn-error.log
17 changes: 9 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,19 @@ script:
- node_modules/.bin/gulp promisetest
- yarn promisefinallytest
- yarn test:phantomjs-single
- node_modules/.bin/karma start karma-dist-sauce-jasmine.conf.js --single-run
- node_modules/.bin/karma start karma-build-sauce-mocha.conf.js --single-run
- node_modules/.bin/karma start karma-dist-sauce-selenium3-jasmine.conf.js --single-run
- node_modules/.bin/karma start karma-build-sauce-selenium3-mocha.conf.js --single-run
- node_modules/.bin/karma start ./test/karma/ci/dist/karma-dist-sauce-jasmine.conf.js --single-run
- node_modules/.bin/karma start ./test/karma/ci/build/karma-build-sauce-mocha.conf.js --single-run
- node_modules/.bin/karma start ./test/karma/ci/dist/karma-dist-sauce-selenium3-jasmine.conf.js --single-run
- node_modules/.bin/karma start ./test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js --single-run
- node_modules/.bin/gulp test/node
- node_modules/.bin/gulp test/node -no-patch-clock
- node_modules/.bin/gulp test/bluebird
- node simple-server.js 2>&1> server.log&
- node ./test/webdriver/test.sauce.js
- node ./test/spec/webdriver/simple-server.js 2>&1> server.log&
- node ./test/spec/webdriver/test.sauce.js
- yarn add [email protected] [email protected] [email protected]
- yarn test:phantomjs-single
- node_modules/.bin/karma start karma-dist-sauce-jasmine3.conf.js --single-run
- node_modules/.bin/karma start karma-build-sauce-selenium3-mocha.conf.js --single-run
- yarn test-mocha-jasmine-bridge-node
- node_modules/.bin/karma start ./test/karma/ci/dist/karma-dist-sauce-jasmine3.conf.js --single-run
- node_modules/.bin/karma start ./test/karma/ci/build/karma-build-sauce-selenium3-mocha.conf.js --single-run
- node_modules/.bin/gulp test/node
- node_modules/.bin/gulp test/node -no-patch-clock
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Implements _Zones_ for JavaScript, inspired by [Dart](https://www.dartlang.org/articles/zones/).

> If you're using zone.js via unpkg (i.e. using `https://unpkg.com/zone.js`)
> and you're using any of the following libraries, make sure you import them first
> and you're using any of the following libraries, make sure you import them first
> * 'newrelic' as it patches global.Promise before zone.js does
> * 'async-listener' as it patches global.setTimeout, global.setInterval before zone.js does
Expand Down Expand Up @@ -36,19 +36,19 @@ See this video from ng-conf 2014 for a detailed explanation:
## Standard API support

zone.js patched most standard web APIs (such as DOM events, `XMLHttpRequest`, ...) and nodejs APIs
(`EventEmitter`, `fs`, ...), for more details, please see [STANDARD-APIS.md](STANDARD-APIS.md).
(`EventEmitter`, `fs`, ...), for more details, please see [STANDARD-APIS.md](./doc/design/STANDARD-APIS.md).

## Nonstandard API support

We are adding support to some nonstandard APIs, such as MediaQuery and
Notification. Please see [NON-STANDARD-APIS.md](NON-STANDARD-APIS.md) for more details.
Notification. Please see [NON-STANDARD-APIS.md](./doc/design/NON-STANDARD-APIS.md) for more details.

## Modules

zone.js patches the async APIs described above, but those patches will have some overhead.
Starting from zone.js v0.8.9, you can choose which web API module you want to patch.
For more details, please
see [MODULE.md](MODULE.md).
see [MODULE.md](./doc/design/MODULE.md).

## Promise A+ test passed
[![Promises/A+ 1.1 compliant](https://promisesaplus.com/assets/logo-small.png)](https://promisesaplus.com/)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
261 changes: 261 additions & 0 deletions doc/design/TEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
# Test Runner

`zone-testing` monkey-patch `jasmine` and `mocha` runner to provide the following functionalities.

1. All `describe/xdescribe/fdescribe` are guaranteed to run in `SyncTestZone`, so no `async` code are allowed under `describe` directly.

```javascript
describe('test', () => {
// setTimeout(() => {}, 100); // not allowed
});
```

2. Support `fakeAsync` in `test`.

```javascript
import 'zone.js`;
import 'zone.js/dist/zone-testing`;

// support simulate timer function.
describe('fakeAsync', () => {
it('setTimeout', fakeAsync(() => {
let called = false;
setTimeout(() => {called = true}, 100);
expect(called).toBe(false);
tick(100);
expect(called).toBe(true);
}));

it ('Promise', fakeAsync(() => {
let thenRan = false;
Promise.resolve(null).then((_) => {
thenRan = true;
});

expect(thenRan).toEqual(false);
flushMicrotasks();
expect(thenRan).toEqual(true);
}));
});
```

Will add more examples later.

3. support `asyncTest`.

```javascript
import 'zone.js`;
import 'zone.js/dist/zone-testing`;
// TODO: this is too complex to load asyncTest, should expose as global function.
const asyncTest = (Zone as any)[Zone.__symbol__('asyncTest')];

describe('async', () => {
it('setTimeout and Promise', asyncTest(() => { // don't need to provide doneFn here.
let timeoutCalled = false;
setTimeout(() => {
timeoutCalled = true;
}, 100);
let thenRan = false;
Promise.resolve(null).then((_) => {
thenRan = true;
});
setTimeout(() => {
expect(timeoutCalled).toBe(true);
expect(thenRan).toBe(true);
}, 200);
}));
});
```

Will add more examples later.

And `asyncTest/fakeAsyncTest/flush/tick/...` those APIs have been exposed as `global APIs`, you can check this type definition file for the full list. [zone-testing.typing.ts](../../lib/testing/zone-testing.typing.ts).

4. Date.now/jasmine.clock()/rxjs.Scheduler support in fakeAsync.

```javascript
describe('fakeAsync', () => {
it('setTimeout', fakeAsync(() => {
const start = Date.now();
testZoneSpec.tick(100);
const end = Date.now();
expect(end - start).toBe(100);
}));
});

// NOTE: automatically fall into fakeAsync need to set this flag to true before loading zone-testing
// (window as any).__zone_symbol__fakeAsyncPatchLock = true;
describe('jasmine.clock', () => {
beforeEach(() => {
jasmine.clock().install();
});

afterEach(() => {
jasmine.clock().uninstall();
});

it('should get date diff correctly', () => { // we don't need fakeAsync here.
// automatically run into fake async zone, because jasmine.clock() is installed.
const start = Date.now();
jasmine.clock().tick(100);
const end = Date.now();
expect(end - start).toBe(100);
});
});

// import the following files to patch Date.now of rxjs.Scheduler/rxjs.asap/rxjs.async
import 'zone.js/dist/zone-patch-rxjs-fakeAsync';

describe('fakeAsync', () => {
it('should get date diff correctly', fakeAsync(() => {
let result = null;
const observable = new Observable((subscribe: any) => {
subscribe.next('hello');
});
observable.delay(1000).subscribe(v => {
result = v;
});
expect(result).toBeNull();
testZoneSpec.tick(1000);
expect(result).toBe('hello');
});
});
```

5. Unify `jasmine/mocha/jest` test cases in `Mocha` runner.

You can write `jasmine`, `mocha`, `jest` style test cases when you use `Mocha` runner.
You can use `jasmine spy` inside `mocha` cases, you can use `jest style expect and mock`, and you can use `jasmine clock` and `jest TimerMock`.

for the full list of supported APIs, please check this type definition file. [zone-testing.typing.ts](../../lib/testing/zone-testing.typing.ts).

```javascript
describe('mixed', () => {
// jasmine style beforeAll
beforeAll(() => {});

// mocha style setup
setup(() => {});

it('jasmine style', () => {
expect(true).toBe(true);
});

// mocha specify
specify('mocha style', () => {
foo = {
setBar: function(value: any) {
bar = value;
}
};

spyOn(foo, 'setBar').and.callThrough();
foo.setBar(123);
expect(bar).toEqual(123);
});

test('jest style', () => {
// TODO: will add type definition later.
(expect([1, 2, 3]) as any).toHaveLength(3);
// can handle promise with jest expect.resolves
return (expect(Promise.resolve('lemon')) as any).resolves.toBe('lemon');
});

test('jest mock', () => {
// can support jest.mock
const myMockFn = jest.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');

// 'first call', 'second call', 'default', 'default'
logs.push(myMockFn(), myMockFn(), myMockFn(), myMockFn());
expect(logs).toEqual(['first call', 'second call', 'default', 'default']);
});
});
```

For full examples, you can find in [jasmine](../../test/spec/mocha/jasmine-bridge.spec.ts) and [jest](../../test/spec/jest-bridge.spec.ts)

And here is the mapping about which `jasmine/jest` functionalities are supported inside `Mocha` runner.

1. BDD/TDD interface.

| jasmine | mocha | jest |
| --- | --- | --- |
| beforeAll | before | beforeAll |
| afterAll | after | beforeAll |
| xdescribe | describe.skip | describe.skip |
| fdescribe | describe.only | describe.only |
| xit | it.skip | it.skip |
| fit | it.only | it.only |

And of course you can use `setup/suiteSetup/tearDown/suiteTearDown` in Mocha.

2. jasmine.clock
You can use `jasmine.clock` inside `Mocha` runner. And you can also use the `auto fakeAsync` feature.

3. jest TimerMock
You can use `jest.useFakeTimers()` to setup jest Timer Mock, and you can use `jest.runAllTimers()/jest.runOnlyPendingTimers()/jest.advanceTimersByTime()/...` to do the time control.

3. jasmine.spy
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/...`

4. jest mock
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`.

5. jasmine expect
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`.

- nothing
- toBe
- toBeCloseTo
- toEqual
- toBeGreaterThan
- toBeGreaterThanOrEqual
- toBeLessThan
- toBeLessThanOrEqual
- toBeDefined
- toBeNaN
- toBeNegativeInfinity
- toBeNull
- toBePositiveInfinity
- toBeUndefined
- toThrow
- toThrowError
- toBeTruthy
- toBeFalsy
- toContain
- toHaveBeenCalled
- toHaveBeenCalledWith
- toMatch
- not

You can also add customMatchers and customEqualityTesters.

6. Jest expect
And you can also get `jest expect matchers`.

- toBeCalled
- toBeCalledWith
- toHaveBeenCalledTimes
- lastCalledWith
- toHaveBeenLastCalledWith
- toBeInstanceOf
- toContainEqual
- toHaveLength
- toHaveProperty
- toMatchObject

And `expect util method`.

- expect.anything
- expect.any
- expect.arrayContaining
- expect.objectContaining
- expect.stringContaining
- expect.stringMatching
- expect.extend
- expect.assertions
- expect.hasAssertions
- expect.resolves (Promise)
- expect.rejects (Promise)
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 12 additions & 7 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ gulp.task('build/zone.js.d.ts', ['compile-esm'], function() {
.pipe(gulp.dest('./dist'));
});

gulp.task('build/zone-testing.d.ts', ['compile-esm'], function() {
return gulp.src('./build-esm/lib/testing/zone-testing.typing.d.ts')
.pipe(rename('zone-testing.d.ts'))
.pipe(gulp.dest('./dist'));
});

// Zone for Node.js environment.
gulp.task('build/zone-node.js', ['compile-esm-node'], function(cb) {
return generateScript('./lib/node/rollup-main.ts', 'zone-node.js', false, cb);
Expand Down Expand Up @@ -404,9 +410,8 @@ function nodeTest(specFiles, cb) {
require('./build/lib/node/rollup-main');
var args = process.argv;
if (args.length > 3) {
require('./build/test/test-env-setup-jasmine' + args[3]);
require('./build/test/env/config/test-env-setup-jasmine' + args[3]);
}
var JasmineRunner = require('jasmine');
var JasmineRunner = require('jasmine');
var jrunner = new JasmineRunner();

Expand All @@ -433,12 +438,12 @@ function nodeTest(specFiles, cb) {
}

gulp.task('test/node', ['compile-node'], function(cb) {
var specFiles = ['build/test/node_entry_point.js'];
var specFiles = ['build/test/env/node/node_entry_point.js'];
nodeTest(specFiles, cb);
});

gulp.task('test/bluebird', ['compile-node'], function(cb) {
var specFiles = ['build/test/node_bluebird_entry_point.js'];
var specFiles = ['build/test/env/node/node_bluebird_entry_point.js'];
nodeTest(specFiles, cb);
});

Expand Down Expand Up @@ -498,7 +503,7 @@ gulp.task('changelog', () => {
// run promise aplus test
gulp.task('promisetest', ['build/zone-node.js'], (cb) => {
const promisesAplusTests = require('promises-aplus-tests');
const adapter = require('./promise-adapter');
const adapter = require('./test/spec/promise/promise-adapter');
promisesAplusTests(adapter, {reporter: 'dot'}, function(err) {
if (err) {
cb(err);
Expand All @@ -510,8 +515,8 @@ gulp.task('promisetest', ['build/zone-node.js'], (cb) => {

// check dist file size limitation
gulp.task('filesize', ['build'], (cb) => {
const checker = require('./check-file-size');
const result = checker(require('./file-size-limit.json'));
const checker = require('./scripts/size/check-file-size');
const result = checker(require('./scripts/size/file-size-limit.json'));
if (result) {
cb();
} else {
Expand Down
12 changes: 0 additions & 12 deletions karma-dist-sauce-jasmine3.conf.js

This file was deleted.

Loading