Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom environments or other setup scripts to bind to Circus events. #8307

Closed
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -3,6 +3,7 @@
### Features

- `[expect]` Improve report when matcher fails, part 15 ([#8281](https://github.com/facebook/jest/pull/8281))
- `[jest-circus]` Allow Circus `addEventHandler` and `getState` to be used externally ([#8307](https://github.com/facebook/jest/pull/8307))
- `[jest-cli]` Update `--forceExit` and "did not exit for one second" message colors ([#8329](https://github.com/facebook/jest/pull/8329))
- `[expect]` Improve report when matcher fails, part 16 ([#8306](https://github.com/facebook/jest/pull/8306))
- `[jest-runner]` Pass docblock pragmas to TestEnvironment constructor ([#8320](https://github.com/facebook/jest/pull/8320))
Expand Down
32 changes: 32 additions & 0 deletions packages/jest-circus/README.md
Expand Up @@ -9,6 +9,38 @@

Circus is a flux-based test runner for Jest that is fast, easy to maintain, and simple to extend.

## API

Circus exposes a public API for interacting with the flux store via imports from `jest-circus`. See the [type definitions](https://github.com/facebook/jest/blob/master/packages/jest-circus/src/types.ts) for more information on the events and state data currently available.

Mutating event or state data is currently unsupported and may cause unexpected behavior or break in a future release without warning.

New events, event data, and/or state data will not be considered a breaking change and may be added in any minor release.

### addEventHandler

`addEventHandler` can be used to listen for parts of the test lifecycle.

```js
import {addEventHandler} from 'jest-circus';

addEventHandler((event, state) => {
if (event.name === 'test_start') {
// Handle event.
}
});
```

### getState

`getState` returns the current state.

```js
import {getState} from 'jest-circus';

const state = getState();
```

## Installation

Install `jest-circus` using yarn:
Expand Down
41 changes: 15 additions & 26 deletions packages/jest-circus/src/__mocks__/testUtils.ts
Expand Up @@ -13,7 +13,7 @@ import crypto from 'crypto';
import {sync as spawnSync, ExecaReturns} from 'execa';
import {skipSuiteOnWindows} from '@jest/test-utils';

const CIRCUS_PATH = require.resolve('../../build');
const CIRCUS_PATH = require.resolve('../../build/');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need to add / after build

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll resolve to the index.js path. It could probably be without build as well, and it'll use main

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding the / wasn't strictly necessary, it's just a result of churn because I changed the import path to something else and then back again

doesn't make a difference either way, it'll resolve to index

const CIRCUS_RUN_PATH = require.resolve('../../build/run');
const CIRCUS_STATE_PATH = require.resolve('../../build/state');
const TEST_EVENT_HANDLER_PATH = require.resolve('./testEventHandler');
Expand All @@ -36,6 +36,7 @@ export const runTest = (source: string) => {
const content = `
require('${BABEL_REGISTER_PATH}')({extensions: [".js", ".ts"]});
const circus = require('${CIRCUS_PATH}');
global.it = circus.it;
global.test = circus.test;
global.describe = circus.describe;
global.beforeEach = circus.beforeEach;
Expand All @@ -55,35 +56,23 @@ export const runTest = (source: string) => {
`;

fs.writeFileSync(tmpFilename, content);
const result = spawnSync('node', [tmpFilename], {
cwd: process.cwd(),
}) as Result;

// For compat with cross-spawn
result.status = result.code;
// Normalize for compat with cross-spawn
let result: Result;

if (result.status !== 0) {
const message = `
STDOUT: ${result.stdout && result.stdout.toString()}
STDERR: ${result.stderr && result.stderr.toString()}
STATUS: ${result.status}
ERROR: ${String(result.error)}
`;
throw new Error(message);
try {
result = spawnSync('node', [tmpFilename], {
cwd: process.cwd(),
}) as Result;
} catch (err) {
result = err;
}

result.stdout = String(result.stdout);
result.stderr = String(result.stderr);

fs.unlinkSync(tmpFilename);

if (result.stderr) {
throw new Error(
`
Unexpected stderr:
${result.stderr}
`,
);
}
return result;
return {
status: result.code,
stderr: String(result.stderr),
stdout: String(result.stdout),
};
};
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`beforeAll is exectued correctly 1`] = `
exports[`beforeAll is executed correctly 1`] = `
"start_describe_definition: describe 1
add_hook: beforeAll
add_test: test 1
Expand Down
79 changes: 19 additions & 60 deletions packages/jest-circus/src/__tests__/circusItTestError.test.ts
Expand Up @@ -5,75 +5,34 @@
* LICENSE file in the root directory of this source tree.
*/

import {Global} from '@jest/types';
import {runTest} from '../__mocks__/testUtils';

let circusIt: Global.It;
let circusTest: Global.It;

// using jest-jasmine2's 'it' to test jest-circus's 'it'. Had to differentiate
// the two with this alias.

const aliasCircusIt = () => {
const {it, test} = require('../');
circusIt = it;
circusTest = test;
};

aliasCircusIt();

describe('test/it error throwing', () => {
it(`it doesn't throw an error with valid arguments`, () => {
expect(() => {
circusIt('test1', () => {});
}).not.toThrowError();
describe('error throwing', () => {
test(`doesn't throw an error with valid arguments`, () => {
expect(runTest(`test('test1', () => {});`).stderr).toEqual('');
});
it(`it throws error with missing callback function`, () => {
expect(() => {
// @ts-ignore: Easy, we're testing runtime errors here
circusIt('test2');
}).toThrowError(

test(`throws error with missing callback function`, () => {
expect(runTest(`test('test2');`).stderr).toContain(
'Missing second argument. It must be a callback function. Perhaps you want to use `test.todo` for a test placeholder.',
);
});
it(`it throws an error when first argument isn't a string`, () => {
expect(() => {
// @ts-ignore: Easy, we're testing runtime errors here
circusIt(() => {});
}).toThrowError('Invalid first argument, () => {}. It must be a string.');
});
it('it throws an error when callback function is not a function', () => {
expect(() => {
// @ts-ignore: Easy, we're testing runtime errors here
circusIt('test4', 'test4b');
}).toThrowError(
'Invalid second argument, test4b. It must be a callback function.',

test(`throws an error when first argument isn't a string`, () => {
expect(runTest(`test(() => {});`).stderr).toContain(
'Invalid first argument, () => {}. It must be a string.',
);
});
it(`test doesn't throw an error with valid arguments`, () => {
expect(() => {
circusTest('test5', () => {});
}).not.toThrowError();
});
it(`test throws error with missing callback function`, () => {
expect(() => {
// @ts-ignore: Easy, we're testing runtime errors here
circusTest('test6');
}).toThrowError(
'Missing second argument. It must be a callback function. Perhaps you want to use `test.todo` for a test placeholder.',

test(`throws an error when callback function is not a function`, () => {
expect(runTest(`test('test4', 'test4b');`).stderr).toContain(
'Invalid second argument, test4b. It must be a callback function.',
);
});
it(`test throws an error when first argument isn't a string`, () => {
expect(() => {
// @ts-ignore: Easy, we're testing runtime errors here
circusTest(() => {});
}).toThrowError('Invalid first argument, () => {}. It must be a string.');
});
it('test throws an error when callback function is not a function', () => {
expect(() => {
// @ts-ignore: Easy, we're testing runtime errors here
circusTest('test8', 'test8b');
}).toThrowError(
'Invalid second argument, test8b. It must be a callback function.',

test(`doesn't throw an error with valid arguments`, () => {
expect(runTest(`test('test4', 'test4b');`).stderr).toContain(
'Invalid second argument, test4b. It must be a callback function.',
);
});
});
2 changes: 1 addition & 1 deletion packages/jest-circus/src/__tests__/hooks.test.ts
Expand Up @@ -56,7 +56,7 @@ test('multiple before each hooks in one describe are executed in the right order
expect(stdout).toMatchSnapshot();
});

test('beforeAll is exectued correctly', () => {
test('beforeAll is executed correctly', () => {
const {stdout} = runTest(`
describe('describe 1', () => {
beforeAll(() => console.log('> beforeAll 1'));
Expand Down