Skip to content

Commit

Permalink
jest-circus Timeouts (jestjs#3760)
Browse files Browse the repository at this point in the history
* jest-circus Timeouts

* setTestTimeout/setTimeout
  • Loading branch information
aaronabramov authored Jun 8, 2017
1 parent e9df793 commit c076947
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 67 deletions.
8 changes: 4 additions & 4 deletions docs/en/JestObjectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The `jest` object is automatically in scope within every test file. The methods
- [`jest.runTimersToTime(msToRun)`](#jestruntimerstotimemstorun)
- [`jest.runOnlyPendingTimers()`](#jestrunonlypendingtimers)
- [`jest.setMock(moduleName, moduleExports)`](#jestsetmockmodulename-moduleexports)
- [`jest.setTestTimeout(timeout)`](#jestsettesttimeouttimeout)
- [`jest.setTimeout(timeout)`](#jestsettimeouttimeout)
- [`jest.unmock(moduleName)`](#jestunmockmodulename)
- [`jest.useFakeTimers()`](#jestusefaketimers)
- [`jest.useRealTimers()`](#jestuserealtimers)
Expand Down Expand Up @@ -205,16 +205,16 @@ Returns the `jest` object for chaining.

*Note It is recommended to use [`jest.mock()`](#jestmockmodulename-factory-options) instead. The `jest.mock` API's second argument is a module factory instead of the expected exported module object.*

### `jest.setTestTimeout(timeout)`
### `jest.setTimeout(timeout)`

Set the default timeout interval for tests in milliseconds.
Set the default timeout interval for tests and before/after hooks in milliseconds.

*Note: The default timeout interval is 5 seconds if this method is not called.*

Example:

```js
jest.setTestTimeout(1000); // 1 second
jest.setTimeout(1000); // 1 second
```

### `jest.unmock(moduleName)`
Expand Down
4 changes: 2 additions & 2 deletions docs/en/Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ Consider replacing the global promise implementation with your own, for example
used Promise libraries to a single one.

If your test is long running, you may want to consider to increase the timeout
by calling `jest.setTestTimeout`
by calling `jest.setTimeout`

```
jest.setTestTimeout(10000); // 10 second timeout
jest.setTimeout(10000); // 10 second timeout
```

### Watchman Issues
Expand Down
13 changes: 0 additions & 13 deletions integration_tests/__tests__/__snapshots__/timeouts-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,6 @@ Ran all test suites.
`;
exports[`exceeds the timeout 1`] = `
" FAIL __tests__/a-banana.js
● banana
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
at ../../packages/jest-jasmine2/build/queueRunner.js:53:21
✕ banana
"
`;
exports[`exceeds the timeout 2`] = `
"Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Expand Down
10 changes: 5 additions & 5 deletions integration_tests/__tests__/timeouts-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ afterAll(() => cleanup(DIR));
test('exceeds the timeout', () => {
writeFiles(DIR, {
'__tests__/a-banana.js': `
jest.setTestTimeout(20);
jest.setTimeout(20);
test('banana', () => {
return new Promise(resolve => {
Expand All @@ -36,15 +36,15 @@ test('exceeds the timeout', () => {

const {stderr, status} = runJest(DIR, ['-w=1', '--ci=false']);
const {rest, summary} = extractSummary(stderr);
expect(status).toBe(1);
expect(rest).toMatchSnapshot();
expect(rest).toMatch(/(jasmine\.DEFAULT_TIMEOUT_INTERVAL|Exceeded timeout)/);
expect(summary).toMatchSnapshot();
expect(status).toBe(1);
});

test('does not exceed the timeout', () => {
writeFiles(DIR, {
'__tests__/a-banana.js': `
jest.setTestTimeout(100);
jest.setTimeout(100);
test('banana', () => {
return new Promise(resolve => {
Expand All @@ -57,7 +57,7 @@ test('does not exceed the timeout', () => {

const {stderr, status} = runJest(DIR, ['-w=1', '--ci=false']);
const {rest, summary} = extractSummary(stderr);
expect(status).toBe(0);
expect(rest).toMatchSnapshot();
expect(summary).toMatchSnapshot();
expect(status).toBe(0);
});
31 changes: 19 additions & 12 deletions packages/jest-circus/src/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ const _runTestsForDescribeBlock = async (describeBlock: DescribeBlock) => {

const _runTest = async (test: TestEntry): Promise<void> => {
const testContext = Object.create(null);

const isSkipped =
test.mode === 'skip' ||
(getState().hasFocusedTests && test.mode !== 'only');

if (isSkipped) {
dispatch({name: 'test_skip', test});
return;
}

const {afterEach, beforeEach} = getEachHooksForTest(test);

for (const hook of beforeEach) {
Expand All @@ -69,27 +79,24 @@ const _runTest = async (test: TestEntry): Promise<void> => {

const _callHook = (hook: Hook, testContext?: TestContext): Promise<any> => {
dispatch({hook, name: 'hook_start'});
return callAsyncFn(hook.fn, testContext, {isHook: true})
const {testTimeout: timeout} = getState();
return callAsyncFn(hook.fn, testContext, {isHook: true, timeout})
.then(() => dispatch({hook, name: 'hook_success'}))
.catch(error => dispatch({error, hook, name: 'hook_failure'}));
};

const _callTest = (test: TestEntry, testContext: TestContext): Promise<any> => {
const isSkipped =
test.mode === 'skip' ||
(getState().hasFocusedTests && test.mode !== 'only');

if (isSkipped) {
dispatch({name: 'test_skip', test});
return Promise.resolve();
}

const _callTest = async (
test: TestEntry,
testContext: TestContext,
): Promise<any> => {
dispatch({name: 'test_start', test});
const {testTimeout: timeout} = getState();

if (!test.fn) {
throw Error(`Tests with no 'fn' should have 'mode' set to 'skipped'`);
}

return callAsyncFn(test.fn, testContext)
return callAsyncFn(test.fn, testContext, {isHook: false, timeout})
.then(() => dispatch({name: 'test_success', test}))
.catch(error => dispatch({error, name: 'test_failure', test}));
};
Expand Down
1 change: 0 additions & 1 deletion packages/jest-circus/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import type {Event, State, EventHandler} from '../types';


const {makeDescribe} = require('./utils');
const eventHandler = require('./eventHandler');

Expand Down
65 changes: 39 additions & 26 deletions packages/jest-circus/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,46 +110,59 @@ const getEachHooksForTest = (
return result;
};

const _makeTimeoutMessage = (timeout, isHook) =>
new Error(
`Exceeded timeout of ${timeout}ms for a ${isHook ? 'hook' : 'test'}.\nUse jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test.`,
);

const callAsyncFn = (
fn: AsyncFn,
testContext: ?TestContext,
{isHook}: {isHook?: boolean} = {isHook: false},
{
isHook,
test,
timeout,
}: {isHook?: ?boolean, test?: TestEntry, timeout: number},
): Promise<any> => {
// If this fn accepts `done` callback we return a promise that fullfills as
// soon as `done` called.
if (fn.length) {
return new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
setTimeout(() => reject(_makeTimeoutMessage(timeout, isHook)), timeout);

// If this fn accepts `done` callback we return a promise that fullfills as
// soon as `done` called.
if (fn.length) {
const done = (reason?: Error | string): void =>
reason ? reject(reason) : resolve();

fn.call(testContext, done);
});
}
return fn.call(testContext, done);
}

let returnedValue;
try {
returnedValue = fn.call(testContext);
} catch (error) {
return Promise.reject(error);
}
let returnedValue;
try {
returnedValue = fn.call(testContext);
} catch (error) {
return reject(error);
}

// If it's a Promise, return it.
if (returnedValue instanceof Promise) {
return returnedValue;
}
// If it's a Promise, return it.
if (returnedValue instanceof Promise) {
return returnedValue.then(resolve, reject);
}

if (!isHook && returnedValue !== void 0) {
throw new Error(
`
if (!isHook && returnedValue !== void 0) {
return reject(
new Error(
`
test functions can only return Promise or undefined.
Returned value: ${String(returnedValue)}
`,
);
}
),
);
}

// Otherwise this test is synchronous, and if it didn't throw it means
// it passed.
return Promise.resolve();
// Otherwise this test is synchronous, and if it didn't throw it means
// it passed.
return resolve();
});
};

const getTestDuration = (test: TestEntry): ?number => {
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-cli/src/__tests__/SearchSource-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
'use strict';

jest.setTestTimeout(15000);
jest.setTimeout(15000);

const path = require('path');
const skipOnWindows = require('skipOnWindows');
Expand Down
4 changes: 2 additions & 2 deletions packages/jest-runtime/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ class Runtime {
const fn = this._moduleMocker.fn.bind(this._moduleMocker);
const spyOn = this._moduleMocker.spyOn.bind(this._moduleMocker);

const setTestTimeout = (timeout: number) => {
const setTimeout = (timeout: number) => {
this._environment.global.jasmine
? (this._environment.global.jasmine.DEFAULT_TIMEOUT_INTERVAL = timeout)
: (this._environment.global[
Expand Down Expand Up @@ -758,7 +758,7 @@ class Runtime {

setMock: (moduleName: string, mock: Object) =>
setMockFactory(moduleName, () => mock),
setTestTimeout,
setTimeout,
spyOn,

unmock,
Expand Down
2 changes: 1 addition & 1 deletion testSetupFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const jasmineReporters = require('jasmine-reporters');

// Some of the `jest-runtime` tests are very slow and cause
// timeouts on travis
jest.setTestTimeout(70000);
jest.setTimeout(70000);

if (global.jasmine && process.env.APPVEYOR_API_URL) {
// Running on AppVeyor, add the custom reporter.
Expand Down

0 comments on commit c076947

Please sign in to comment.