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

expect: Improve report when assertion fails, part 4 #7241

Merged
merged 15 commits into from
Dec 18, 2018
Merged
Show file tree
Hide file tree
Changes from 4 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
387 changes: 208 additions & 179 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap

Large diffs are not rendered by default.

456 changes: 228 additions & 228 deletions packages/expect/src/__tests__/__snapshots__/spy_matchers.test.js.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,19 @@ Instead, it threw:
`;

exports[`.toThrow() invalid actual 1`] = `
"<dim>expect(</><red>function</><dim>).toThrow(</><green>undefined</><dim>)</>
"<dim>expect(</><red>received</><dim>)[.not].toThrow(</><green>expected</><dim>)</>

Received value must be a function, but instead \\"string\\" was found"
<bold>Matcher error</>: Received value must be a function

Received string: <red>\\"a string\\"</>"
`;

exports[`.toThrow() invalid arguments 1`] = `
"<dim>expect(</><red>function</><dim>).not.toThrow(</><green>number</><dim>)</>
"<dim>expect(</><red>received</><dim>)[.not].toThrow(</><green>expected</><dim>)</>

<bold>Matcher error</>: Expected value must be a string or regular expression or Error

Unexpected argument passed.
Expected: <green>\\"string\\"</>, <green>\\"Error (type)\\"</> or <green>\\"regexp\\"</>.
Got:
string: <green>\\"111\\"</>"
Expected number: <green>111</>"
`;

exports[`.toThrow() promise/async throws if Error-like object is returned did not throw at all 1`] = `
Expand Down Expand Up @@ -154,18 +155,19 @@ Instead, it threw:
`;

exports[`.toThrowError() invalid actual 1`] = `
"<dim>expect(</><red>function</><dim>).toThrowError(</><green>undefined</><dim>)</>
"<dim>expect(</><red>received</><dim>)[.not].toThrowError(</><green>expected</><dim>)</>

Received value must be a function, but instead \\"string\\" was found"
<bold>Matcher error</>: Received value must be a function

Received string: <red>\\"a string\\"</>"
`;

exports[`.toThrowError() invalid arguments 1`] = `
"<dim>expect(</><red>function</><dim>).not.toThrowError(</><green>number</><dim>)</>
"<dim>expect(</><red>received</><dim>)[.not].toThrowError(</><green>expected</><dim>)</>

<bold>Matcher error</>: Expected value must be a string or regular expression or Error

Unexpected argument passed.
Expected: <green>\\"string\\"</>, <green>\\"Error (type)\\"</> or <green>\\"regexp\\"</>.
Got:
string: <green>\\"111\\"</>"
Expected number: <green>111</>"
`;

exports[`.toThrowError() promise/async throws if Error-like object is returned did not throw at all 1`] = `
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/src/__tests__/assertion_counts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe('.hasAssertions()', () => {
it('throws if passed parameters', () => {
jestExpect(() => {
jestExpect.hasAssertions(2);
}).toThrow(/does not accept any arguments/);
}).toThrow(/Expected value must be omitted or undefined/);
});

it('hasAssertions not leaking to global state', () => {});
Expand Down
6 changes: 6 additions & 0 deletions packages/expect/src/__tests__/matchers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,12 @@ describe('.toHaveLength', () => {
jestExpect(undefined).toHaveLength(1),
).toThrowErrorMatchingSnapshot();
});

test('matcher error expected length', () => {
expect(() =>
jestExpect('abc').toHaveLength('3'),
).toThrowErrorMatchingSnapshot();
});
});

describe('.toHaveProperty()', () => {
Expand Down
18 changes: 8 additions & 10 deletions packages/expect/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,15 @@ const makeResolveMatcher = (
const matcherStatement = `.resolves.${isNot ? 'not.' : ''}${matcherName}`;
if (!isPromise(actual)) {
throw new JestAssertionError(
matcherUtils.matcherHint(matcherStatement, 'received', '') +
'\n\n' +
`${matcherUtils.RECEIVED_COLOR(
'received',
)} value must be a Promise.\n` +
matcherUtils.matcherErrorMessage(
matcherUtils.matcherHint(matcherStatement, undefined, ''),
'Received value must be a Promise',
matcherUtils.printWithType(
'Received',
actual,
matcherUtils.printReceived,
),
),
);
}

Expand Down Expand Up @@ -187,16 +186,15 @@ const makeRejectMatcher = (
const matcherStatement = `.rejects.${isNot ? 'not.' : ''}${matcherName}`;
if (!isPromise(actual)) {
throw new JestAssertionError(
matcherUtils.matcherHint(matcherStatement, 'received', '') +
'\n\n' +
`${matcherUtils.RECEIVED_COLOR(
'received',
)} value must be a Promise.\n` +
matcherUtils.matcherErrorMessage(
matcherUtils.matcherHint(matcherStatement, undefined, ''),
'Received value must be a Promise',
matcherUtils.printWithType(
'Received',
actual,
matcherUtils.printReceived,
),
),
);
}

Expand Down
133 changes: 80 additions & 53 deletions packages/expect/src/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
SUGGEST_TO_CONTAIN_EQUAL,
ensureNoExpected,
ensureNumbers,
matcherErrorMessage,
matcherHint,
printReceived,
printExpected,
Expand Down Expand Up @@ -156,12 +157,13 @@ const matchers: MatchersObject = {

if (constType !== 'function') {
throw new Error(
matcherHint('.toBeInstanceOf', 'value', 'constructor', {
isNot: this.isNot,
}) +
`\n\n` +
`Expected constructor to be a function. Instead got:\n` +
` ${printExpected(constType)}`,
matcherErrorMessage(
matcherHint('.toBeInstanceOf', undefined, undefined, {
isNot: this.isNot,
}),
'Expected value must be a function',
printWithType('Expected', constructor, printExpected),
),
);
}
const pass = received instanceof constructor;
Expand Down Expand Up @@ -282,12 +284,13 @@ const matchers: MatchersObject = {
converted = Array.from(collection);
} catch (e) {
throw new Error(
matcherHint('[.not].toContainEqual', 'collection', 'value') +
'\n\n' +
`Expected ${RECEIVED_COLOR(
'collection',
)} to be an array-like structure.\n` +
matcherErrorMessage(
matcherHint('.toContain', undefined, undefined, {
isNot: this.isNot,
}),
'Received value cannot be null nor undefined',
printWithType('Received', collection, printReceived),
),
);
}
}
Expand Down Expand Up @@ -334,12 +337,13 @@ const matchers: MatchersObject = {
converted = Array.from(collection);
} catch (e) {
throw new Error(
matcherHint('[.not].toContainEqual', 'collection', 'value') +
'\n\n' +
`Expected ${RECEIVED_COLOR(
'collection',
)} to be an array-like structure.\n` +
matcherErrorMessage(
matcherHint('.toContainEqual', undefined, undefined, {
isNot: this.isNot,
}),
'Received value cannot be null nor undefined',
printWithType('Received', collection, printReceived),
),
);
}
}
Expand Down Expand Up @@ -404,14 +408,25 @@ const matchers: MatchersObject = {
(!received || typeof received.length !== 'number')
) {
throw new Error(
matcherHint('[.not].toHaveLength', 'received', 'length') +
'\n\n' +
`Expected value to have a 'length' property that is a number. ` +
`Received:\n` +
` ${printReceived(received)}\n` +
(received
? `received.length:\n ${printReceived(received.length)}`
: ''),
matcherErrorMessage(
matcherHint('.toHaveLength', undefined, undefined, {
isNot: this.isNot,
}),
'Received value must have a length property whose value must be a number',
printWithType('Received', received, printReceived),
),
);
}

if (typeof length !== 'number') {
throw new Error(
matcherErrorMessage(
matcherHint('.toHaveLength', undefined, undefined, {
isNot: this.isNot,
}),
'Expected value must be a number',
printWithType('Expected', length, printExpected),
),
);
}

Expand Down Expand Up @@ -443,29 +458,31 @@ const matchers: MatchersObject = {
const valuePassed = arguments.length === 3;
const secondArgument = valuePassed ? 'value' : null;

if (!object && typeof object !== 'string' && typeof object !== 'number') {
if (object === null || object === undefined) {
throw new Error(
matcherHint('[.not].toHaveProperty', 'object', 'path', {
secondArgument,
}) +
'\n\n' +
`Expected ${RECEIVED_COLOR('object')} to be an object. Received:\n` +
` ${getType(object)}: ${printReceived(object)}`,
matcherErrorMessage(
matcherHint('.toHaveProperty', undefined, 'path', {
isNot: this.isNot,
secondArgument,
}),
'Received value cannot be null nor undefined',
printWithType('Received', object, printReceived),
),
);
}

const keyPathType = getType(keyPath);

if (keyPathType !== 'string' && keyPathType !== 'array') {
throw new Error(
matcherHint('[.not].toHaveProperty', 'object', 'path', {
secondArgument,
}) +
'\n\n' +
`Expected ${EXPECTED_COLOR(
'path',
)} to be a string or an array. Received:\n` +
` ${keyPathType}: ${printReceived(keyPath)}`,
matcherErrorMessage(
matcherHint('.toHaveProperty', undefined, 'path', {
isNot: this.isNot,
secondArgument,
}),
'Expected path must be a string or array',
printWithType('Expected', keyPath, printExpected),
),
);
}

Expand Down Expand Up @@ -527,10 +544,13 @@ const matchers: MatchersObject = {
toMatch(received: string, expected: string | RegExp) {
if (typeof received !== 'string') {
throw new Error(
matcherHint('[.not].toMatch', 'string', 'expected') +
'\n\n' +
`${RECEIVED_COLOR('string')} value must be a string.\n` +
matcherErrorMessage(
matcherHint('.toMatch', undefined, undefined, {
isNot: this.isNot,
}),
'Received value must be a string',
printWithType('Received', received, printReceived),
),
);
}

Expand All @@ -539,12 +559,13 @@ const matchers: MatchersObject = {
!(typeof expected === 'string')
) {
throw new Error(
matcherHint('[.not].toMatch', 'string', 'expected') +
'\n\n' +
`${EXPECTED_COLOR(
'expected',
)} value must be a string or a regular expression.\n` +
matcherErrorMessage(
matcherHint('.toMatch', undefined, undefined, {
isNot: this.isNot,
}),
'Expected value must be a string or regular expression',
printWithType('Expected', expected, printExpected),
),
);
}

Expand All @@ -571,19 +592,25 @@ const matchers: MatchersObject = {
toMatchObject(receivedObject: Object, expectedObject: Object) {
if (typeof receivedObject !== 'object' || receivedObject === null) {
throw new Error(
matcherHint('[.not].toMatchObject', 'object', 'expected') +
'\n\n' +
`${RECEIVED_COLOR('received')} value must be an object.\n` +
matcherErrorMessage(
matcherHint('.toMatchObject', undefined, undefined, {
isNot: this.isNot,
}),
'Received value must be a non-null object',
printWithType('Received', receivedObject, printReceived),
),
);
}

if (typeof expectedObject !== 'object' || expectedObject === null) {
throw new Error(
matcherHint('[.not].toMatchObject', 'object', 'expected') +
'\n\n' +
`${EXPECTED_COLOR('expected')} value must be an object.\n` +
matcherErrorMessage(
matcherHint('.toMatchObject', undefined, undefined, {
isNot: this.isNot,
}),
'Expected value must be a non-null object',
printWithType('Expected', expectedObject, printExpected),
),
);
}

Expand Down
9 changes: 5 additions & 4 deletions packages/expect/src/spy_matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ensureExpectedIsNumber,
ensureNoExpected,
EXPECTED_COLOR,
matcherErrorMessage,
matcherHint,
pluralize,
printExpected,
Expand Down Expand Up @@ -465,11 +466,11 @@ const ensureMock = (mockOrSpy, matcherName) => {
mockOrSpy._isMockFunction !== true)
) {
throw new Error(
matcherHint('[.not]' + matcherName, 'jest.fn()', '') +
'\n\n' +
`${RECEIVED_COLOR('jest.fn()')} value must be a mock function ` +
`or spy.\n` +
matcherErrorMessage(
matcherHint('[.not]' + matcherName, 'jest.fn()', ''),
'Received value must be a mock or spy function',
printWithType('Received', mockOrSpy, printReceived),
),
);
}
};
Expand Down
Loading