Skip to content

Commit

Permalink
Make rejects and resolves synchronous (#5364)
Browse files Browse the repository at this point in the history
* Make `makeRejectMatcher` synchronizable (#5361)

* Reorganize code using promise instaed of async/await

* Update changelog

* Move rejectedHandler onto then

* Make `makeResolveMatcher` synchronizable

* Update a snapshot

* Update CHANGELOG.md

* Update CHANGELOG.md
  • Loading branch information
incleaf authored and cpojer committed Jan 24, 2018
1 parent 4080d98 commit 4585c73
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,8 @@
([#5367](https://github.com/facebook/jest/pull/5367))
* `[jest-cli]` Fix npm update command for snapshot summary.
([#5376](https://github.com/facebook/jest/pull/5376))
* `[expect]` Make `rejects` and `resolves` synchronously validate its argument.
([#5364](https://github.com/facebook/jest/pull/5364))

## jest 22.1.4

Expand Down
Expand Up @@ -118,7 +118,7 @@ exports[`works with async failures 1`] = `
+ \\"foo\\": \\"bar\\",
}
at ../../packages/expect/build/index.js:156:54
at ../../packages/expect/build/index.js:145:57
"
`;
Expand Down
62 changes: 62 additions & 0 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Expand Up @@ -84,6 +84,14 @@ Received:
string: <red>\\"a\\"</>"
`;

exports[`.resolves fails non-promise value "a" synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received:
string: <red>\\"a\\"</>"
`;

exports[`.resolves fails non-promise value [1] 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

Expand All @@ -92,6 +100,14 @@ Received:
array: <red>[1]</>"
`;

exports[`.resolves fails non-promise value [1] synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received:
array: <red>[1]</>"
`;

exports[`.resolves fails non-promise value [Function anonymous] 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

Expand All @@ -100,6 +116,14 @@ Received:
function: <red>[Function anonymous]</>"
`;

exports[`.resolves fails non-promise value [Function anonymous] synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received:
function: <red>[Function anonymous]</>"
`;

exports[`.resolves fails non-promise value {"a": 1} 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

Expand All @@ -108,6 +132,14 @@ Received:
object: <red>{\\"a\\": 1}</>"
`;

exports[`.resolves fails non-promise value {"a": 1} synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received:
object: <red>{\\"a\\": 1}</>"
`;

exports[`.resolves fails non-promise value 4 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

Expand All @@ -116,13 +148,28 @@ Received:
number: <red>4</>"
`;

exports[`.resolves fails non-promise value 4 synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received:
number: <red>4</>"
`;

exports[`.resolves fails non-promise value null 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received: <red>null</>"
`;

exports[`.resolves fails non-promise value null synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received: <red>null</>"
`;

exports[`.resolves fails non-promise value true 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

Expand All @@ -131,13 +178,28 @@ Received:
boolean: <red>true</>"
`;

exports[`.resolves fails non-promise value true synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received:
boolean: <red>true</>"
`;

exports[`.resolves fails non-promise value undefined 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received: <red>undefined</>"
`;

exports[`.resolves fails non-promise value undefined synchronously 1`] = `
"<dim>expect(</><red>received</><dim>).resolves.toBeDefined(</><dim>)</>

<red>received</> value must be a Promise.
Received: <red>undefined</>"
`;

exports[`.toBe() does not crash on circular references 1`] = `
"<dim>expect(</><red>received</><dim>).toBe(</><green>expected</><dim>)</>

Expand Down
25 changes: 25 additions & 0 deletions packages/expect/src/__tests__/matchers.test.js
Expand Up @@ -37,6 +37,18 @@ describe('.rejects', () => {
await jestExpect(fn()).rejects.toThrow('some error');
});

[4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => {
it(`fails non-promise value ${stringify(value)} synchronously`, () => {
let error;
try {
jestExpect(value).rejects.toBe(111);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
});
});

[4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => {
it(`fails non-promise value ${stringify(value)}`, async () => {
let error;
Expand Down Expand Up @@ -81,6 +93,19 @@ describe('.resolves', () => {
).resolves.toThrow();
});

[4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => {
it(`fails non-promise value ${stringify(value)} synchronously`, () => {
let error;
try {
jestExpect(value).resolves.toBeDefined();
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toMatchSnapshot();
});
});

[4, [1], {a: 1}, 'a', true, null, undefined, () => {}].forEach(value => {
it(`fails non-promise value ${stringify(value)}`, async () => {
let error;
Expand Down
55 changes: 27 additions & 28 deletions packages/expect/src/index.js
Expand Up @@ -130,7 +130,7 @@ const makeResolveMatcher = (
matcher: RawMatcherFn,
isNot: boolean,
actual: Promise<any>,
): PromiseMatcherFn => async (...args) => {
): PromiseMatcherFn => (...args) => {
const matcherStatement = `.resolves.${isNot ? 'not.' : ''}${matcherName}`;
if (!isPromise(actual)) {
throw new JestAssertionError(
Expand All @@ -141,27 +141,27 @@ const makeResolveMatcher = (
);
}

let result;
try {
result = await actual;
} catch (e) {
throw new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') +
'\n\n' +
`Expected ${utils.RECEIVED_COLOR('received')} Promise to resolve, ` +
'instead it rejected to value\n' +
` ${utils.printReceived(e)}`,
);
}
return makeThrowingMatcher(matcher, isNot, result).apply(null, args);
return actual.then(
result => makeThrowingMatcher(matcher, isNot, result).apply(null, args),
reason => {
const err = new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') +
'\n\n' +
`Expected ${utils.RECEIVED_COLOR('received')} Promise to resolve, ` +
'instead it rejected to value\n' +
` ${utils.printReceived(reason)}`,
);
return Promise.reject(err);
},
);
};

const makeRejectMatcher = (
matcherName: string,
matcher: RawMatcherFn,
isNot: boolean,
actual: Promise<any>,
): PromiseMatcherFn => async (...args) => {
): PromiseMatcherFn => (...args) => {
const matcherStatement = `.rejects.${isNot ? 'not.' : ''}${matcherName}`;
if (!isPromise(actual)) {
throw new JestAssertionError(
Expand All @@ -172,19 +172,18 @@ const makeRejectMatcher = (
);
}

let result;
try {
result = await actual;
} catch (e) {
return makeThrowingMatcher(matcher, isNot, e).apply(null, args);
}

throw new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') +
'\n\n' +
`Expected ${utils.RECEIVED_COLOR('received')} Promise to reject, ` +
'instead it resolved to value\n' +
` ${utils.printReceived(result)}`,
return actual.then(
result => {
const err = new JestAssertionError(
utils.matcherHint(matcherStatement, 'received', '') +
'\n\n' +
`Expected ${utils.RECEIVED_COLOR('received')} Promise to reject, ` +
'instead it resolved to value\n' +
` ${utils.printReceived(result)}`,
);
return Promise.reject(err);
},
reason => makeThrowingMatcher(matcher, isNot, reason).apply(null, args),
);
};

Expand Down

0 comments on commit 4585c73

Please sign in to comment.