From f04e1b109ec3fb6ec1b6cab9d6dc235b92f3c09f Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Sun, 13 Jan 2019 16:57:03 -0500 Subject: [PATCH 01/14] expect: Improve report when assertion fails, part 6 --- .../toThrowMatchers.test.js.snap | 256 +++++++------- .../src/__tests__/toThrowMatchers.test.js | 8 +- packages/expect/src/index.js | 2 +- packages/expect/src/toThrowMatchers.js | 314 ++++++++++-------- 4 files changed, 301 insertions(+), 279 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap index f02f4d14a79c..a0e0d4d3c525 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap @@ -1,35 +1,37 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`.toThrow() error class did not throw at all 1`] = ` -"expect(function).toThrow(type) +"expect(received).toThrow(expected) -Expected the function to throw an error of type: - \\"Err\\" -But it didn't throw anything." +Expected error name: \\"Err\\" + +Received function did not throw an exception" `; exports[`.toThrow() error class threw, but class did not match 1`] = ` -"expect(function).toThrow(type) +"expect(received).toThrow(expected) + +Expected error name: \\"Err2\\" +Received error name: \\"Error\\" -Expected the function to throw an error of type: - \\"Err2\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" +Received error message: \\"apple\\" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() error class threw, but should not have 1`] = ` -"expect(function).not.toThrow(type) +"expect(received).not.toThrow(expected) + +Expected error name: \\"Err\\" +Received error name: \\"Error\\" -Expected the function not to throw an error of type: - \\"Err\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" +Received error message: \\"apple\\" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() invalid actual 1`] = ` -"expect(received)[.not].toThrow(expected) +"expect(received).toThrow() Matcher error: received value must be a function @@ -38,126 +40,124 @@ Received has value: \\"a string\\"" `; exports[`.toThrow() invalid arguments 1`] = ` -"expect(received)[.not].toThrow(expected) +"expect(received).not.toThrow(expected) -Matcher error: expected value must be a string or regular expression or Error +Matcher error: expected value must be a string or regular expression or class or error Expected has type: number Expected has value: 111" `; exports[`.toThrow() promise/async throws if Error-like object is returned did not throw at all 1`] = ` -[Error: expect(function).toThrow(undefined) +[Error: expect(received).rejects.toThrow() -Expected the function to throw an error. -But it didn't throw anything.] +Received function did not throw an exception] `; exports[`.toThrow() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` -[Error: expect(function).toThrow(type) +[Error: expect(received).rejects.toThrow(expected) + +Expected error name: "Err2" +Received error name: "Error" + +Received error message: "async apple" -Expected the function to throw an error of type: - "Err2" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] `; exports[`.toThrow() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` -[Error: expect(function).not.toThrow() +[Error: expect(received).rejects.not.toThrow() -Expected the function not to throw an error. -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] +Received error name: "Error" +Received error message: "async apple" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] `; exports[`.toThrow() regexp did not throw at all 1`] = ` -"expect(function).toThrow(regexp) +"expect(received).toThrow(expected) + +Expected error pattern: /apple/ -Expected the function to throw an error matching: - /apple/ -But it didn't throw anything." +Received function did not throw an exception" `; exports[`.toThrow() regexp threw, but message did not match 1`] = ` -"expect(function).toThrow(regexp) +"expect(received).toThrow(expected) -Expected the function to throw an error matching: - /banana/ -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" +Expected error pattern: /banana/ +Received error message: \\"apple\\" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() regexp threw, but should not have 1`] = ` -"expect(function).not.toThrow(regexp) +"expect(received).not.toThrow(expected) + +Expected error pattern: /apple/ +Received error message: \\"apple\\" -Expected the function not to throw an error matching: - /apple/ -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() strings did not throw at all 1`] = ` -"expect(function).toThrow(string) +"expect(received).toThrow(expected) -Expected the function to throw an error matching: - \\"apple\\" -But it didn't throw anything." +Expected error pattern: \\"apple\\" + +Received function did not throw an exception" `; exports[`.toThrow() strings threw, but message did not match 1`] = ` -"expect(function).toThrow(string) +"expect(received).toThrow(expected) + +Expected error pattern: \\"banana\\" +Received error message: \\"apple\\" -Expected the function to throw an error matching: - \\"banana\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() strings threw, but should not have 1`] = ` -"expect(function).not.toThrow(string) +"expect(received).not.toThrow(expected) + +Expected error pattern: \\"apple\\" +Received error message: \\"apple\\" -Expected the function not to throw an error matching: - \\"apple\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() error class did not throw at all 1`] = ` -"expect(function).toThrowError(type) +"expect(received).toThrowError(expected) -Expected the function to throw an error of type: - \\"Err\\" -But it didn't throw anything." +Expected error name: \\"Err\\" + +Received function did not throw an exception" `; exports[`.toThrowError() error class threw, but class did not match 1`] = ` -"expect(function).toThrowError(type) +"expect(received).toThrowError(expected) + +Expected error name: \\"Err2\\" +Received error name: \\"Error\\" -Expected the function to throw an error of type: - \\"Err2\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" +Received error message: \\"apple\\" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() error class threw, but should not have 1`] = ` -"expect(function).not.toThrowError(type) +"expect(received).not.toThrowError(expected) + +Expected error name: \\"Err\\" +Received error name: \\"Error\\" -Expected the function not to throw an error of type: - \\"Err\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" +Received error message: \\"apple\\" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() invalid actual 1`] = ` -"expect(received)[.not].toThrowError(expected) +"expect(received).toThrowError() Matcher error: received value must be a function @@ -166,92 +166,88 @@ Received has value: \\"a string\\"" `; exports[`.toThrowError() invalid arguments 1`] = ` -"expect(received)[.not].toThrowError(expected) +"expect(received).not.toThrowError(expected) -Matcher error: expected value must be a string or regular expression or Error +Matcher error: expected value must be a string or regular expression or class or error Expected has type: number Expected has value: 111" `; exports[`.toThrowError() promise/async throws if Error-like object is returned did not throw at all 1`] = ` -[Error: expect(function).toThrow(undefined) +[Error: expect(received).rejects.toThrowError() -Expected the function to throw an error. -But it didn't throw anything.] +Received function did not throw an exception] `; exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` -[Error: expect(function).toThrow(type) +[Error: expect(received).rejects.toThrowError(expected) + +Expected error name: "Err2" +Received error name: "Error" + +Received error message: "async apple" -Expected the function to throw an error of type: - "Err2" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] `; exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` -[Error: expect(function).not.toThrow() +[Error: expect(received).rejects.not.toThrowError() -Expected the function not to throw an error. -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] +Received error name: "Error" +Received error message: "async apple" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] `; exports[`.toThrowError() regexp did not throw at all 1`] = ` -"expect(function).toThrowError(regexp) +"expect(received).toThrowError(expected) + +Expected error pattern: /apple/ -Expected the function to throw an error matching: - /apple/ -But it didn't throw anything." +Received function did not throw an exception" `; exports[`.toThrowError() regexp threw, but message did not match 1`] = ` -"expect(function).toThrowError(regexp) +"expect(received).toThrowError(expected) -Expected the function to throw an error matching: - /banana/ -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" +Expected error pattern: /banana/ +Received error message: \\"apple\\" + + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() regexp threw, but should not have 1`] = ` -"expect(function).not.toThrowError(regexp) +"expect(received).not.toThrowError(expected) + +Expected error pattern: /apple/ +Received error message: \\"apple\\" -Expected the function not to throw an error matching: - /apple/ -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() strings did not throw at all 1`] = ` -"expect(function).toThrowError(string) +"expect(received).toThrowError(expected) -Expected the function to throw an error matching: - \\"apple\\" -But it didn't throw anything." +Expected error pattern: \\"apple\\" + +Received function did not throw an exception" `; exports[`.toThrowError() strings threw, but message did not match 1`] = ` -"expect(function).toThrowError(string) +"expect(received).toThrowError(expected) + +Expected error pattern: \\"banana\\" +Received error message: \\"apple\\" -Expected the function to throw an error matching: - \\"banana\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() strings threw, but should not have 1`] = ` -"expect(function).not.toThrowError(string) +"expect(received).not.toThrowError(expected) + +Expected error pattern: \\"apple\\" +Received error message: \\"apple\\" -Expected the function not to throw an error matching: - \\"apple\\" -Instead, it threw: - Error - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.js b/packages/expect/src/__tests__/toThrowMatchers.test.js index 1aa0efbb7e72..fd7121fa55fa 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.js +++ b/packages/expect/src/__tests__/toThrowMatchers.test.js @@ -201,7 +201,7 @@ class customError extends Error { test('did not throw at all', async () => { let err; try { - await jestExpect(asyncFn()).rejects.toThrow(); + await jestExpect(asyncFn()).rejects[toThrow](); } catch (error) { err = error; } @@ -211,7 +211,7 @@ class customError extends Error { test('threw, but class did not match', async () => { let err; try { - await jestExpect(asyncFn(true)).rejects.toThrow(Err2); + await jestExpect(asyncFn(true)).rejects[toThrow](Err2); } catch (error) { err = error; } @@ -221,7 +221,7 @@ class customError extends Error { test('threw, but should not have', async () => { let err; try { - await jestExpect(asyncFn(true)).rejects.not.toThrow(); + await jestExpect(asyncFn(true)).rejects.not[toThrow](); } catch (error) { err = error; } @@ -231,7 +231,7 @@ class customError extends Error { test('invalid arguments', () => { expect(() => - jestExpect(() => {})[toThrow](111), + jestExpect(() => {}).not[toThrow](111), ).toThrowErrorMatchingSnapshot(); }); diff --git a/packages/expect/src/index.js b/packages/expect/src/index.js index c570fb8a41a9..7f5f517cb0cb 100644 --- a/packages/expect/src/index.js +++ b/packages/expect/src/index.js @@ -66,7 +66,7 @@ const createToThrowErrorMatchingSnapshotMatcher = function(matcher) { const getPromiseMatcher = (name, matcher) => { if (name === 'toThrow' || name === 'toThrowError') { - return createThrowMatcher('.' + name, true); + return createThrowMatcher(name, true); } else if ( name === 'toThrowErrorMatchingSnapshot' || name === 'toThrowErrorMatchingInlineSnapshot' diff --git a/packages/expect/src/toThrowMatchers.js b/packages/expect/src/toThrowMatchers.js index af2ae6c01275..fad9f57bdbe9 100644 --- a/packages/expect/src/toThrowMatchers.js +++ b/packages/expect/src/toThrowMatchers.js @@ -9,201 +9,227 @@ import type {MatchersObject} from 'types/Matchers'; -import getType from 'jest-get-type'; -import {escapeStrForRegex} from 'jest-regex-util'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import { EXPECTED_COLOR, RECEIVED_COLOR, - highlightTrailingWhitespace, matcherErrorMessage, matcherHint, printExpected, printReceived, printWithType, } from 'jest-matcher-utils'; -import {equals} from './jasmineUtils'; import {isError} from './utils'; -export const createMatcher = (matcherName: string, fromPromise?: boolean) => ( - actual: Function, - expected: string | Error | RegExp, -) => { - const value = expected; - let error; - - if (fromPromise && isError(actual)) { - error = actual; - } else { - if (typeof actual !== 'function') { - if (!fromPromise) { - throw new Error( - matcherErrorMessage( - matcherHint('[.not]' + matcherName, undefined, undefined), - `${RECEIVED_COLOR('received')} value must be a function`, - printWithType('Received', actual, printReceived), - ), - ); - } +const DID_NOT_THROW = 'Received function did not throw an exception'; + +export const createMatcher = (matcherName: string, fromPromise?: boolean) => + function(received: Function, expected: string | Error | RegExp) { + const options = { + isNot: this.isNot, + promise: this.promise, + }; + + let error; + + if (fromPromise && isError(received)) { + error = received; } else { - try { - actual(); - } catch (e) { - error = e; + if (typeof received !== 'function') { + if (!fromPromise) { + const placeholder = expected === undefined ? '' : 'expected'; + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, placeholder, options), + `${RECEIVED_COLOR('received')} value must be a function`, + printWithType('Received', received, printReceived), + ), + ); + } + } else { + try { + received(); + } catch (e) { + error = e; + } } } - } - - if (typeof expected === 'string') { - expected = new RegExp(escapeStrForRegex(expected)); - } - - if (typeof expected === 'function') { - return toThrowMatchingError(matcherName, error, expected); - } else if (expected && typeof expected.test === 'function') { - return toThrowMatchingStringOrRegexp( - matcherName, - error, - (expected: any), - value, - ); - } else if (expected && typeof expected === 'object') { - return toThrowMatchingErrorInstance(matcherName, error, (expected: any)); - } else if (expected === undefined) { - const pass = error !== undefined; - return { - message: pass - ? () => - matcherHint('.not' + matcherName, 'function', '') + - '\n\n' + - 'Expected the function not to throw an error.\n' + - printActualErrorMessage(error) - : () => - matcherHint(matcherName, 'function', getType(value)) + - '\n\n' + - 'Expected the function to throw an error.\n' + - printActualErrorMessage(error), - pass, - }; - } else { - throw new Error( - matcherErrorMessage( - matcherHint('[.not]' + matcherName, undefined, undefined), - `${EXPECTED_COLOR( - 'expected', - )} value must be a string or regular expression or Error`, - printWithType('Expected', expected, printExpected), - ), - ); - } -}; + + if (error && !error.message && !error.name) { + error = new Error(error); + } + + const expectedType = typeof expected; + if (expectedType === 'undefined') { + return toThrow(matcherName, options, error); + } else if (expectedType === 'function') { + return toThrowExpectedClass(matcherName, options, error, expected); + } else if (expectedType === 'string') { + return toThrowExpectedString(matcherName, options, error, expected); + } else if (expected && typeof expected.test === 'function') { + return toThrowExpectedRegExp(matcherName, options, error, expected); + } else if (expected && expectedType === 'object') { + return toThrowExpectedObject(matcherName, options, error, expected); + } else { + throw new Error( + matcherErrorMessage( + matcherHint(matcherName, undefined, undefined, options), + `${EXPECTED_COLOR( + 'expected', + )} value must be a string or regular expression or class or error`, + printWithType('Expected', expected, printExpected), + ), + ); + } + }; const matchers: MatchersObject = { - toThrow: createMatcher('.toThrow'), - toThrowError: createMatcher('.toThrowError'), + toThrow: createMatcher('toThrow'), + toThrowError: createMatcher('toThrowError'), }; -const toThrowMatchingStringOrRegexp = ( - name: string, - error: ?Error, - pattern: RegExp, - value: RegExp | string | Error, +const toThrowExpectedString = ( + matcherName: string, + options: MatcherHintOptions, + error?: Error, + expected: string, ) => { - if (error && !error.message && !error.name) { - error = new Error(error); - } + const isDefined = error !== undefined; + const pass = isDefined && error.message.includes(expected); - const pass = !!(error && error.message.match(pattern)); const message = pass ? () => - matcherHint('.not' + name, 'function', getType(value)) + + matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected the function not to throw an error matching:\n` + - ` ${printExpected(value)}\n` + - printActualErrorMessage(error) + `Expected error pattern: ${printExpected(expected)}\n` + + // Possible improvement also for toMatch + // inverse highlight matching substring: + `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) : () => - matcherHint(name, 'function', getType(value)) + + matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected the function to throw an error matching:\n` + - ` ${printExpected(value)}\n` + - printActualErrorMessage(error); + `Expected error pattern: ${printExpected(expected)}\n` + + (isDefined + ? `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) + : '\n' + DID_NOT_THROW); return {message, pass}; }; -const toThrowMatchingErrorInstance = ( - name: string, - error: ?Error, - expectedError: Error, +const toThrowExpectedRegExp = ( + matcherName: string, + options: MatcherHintOptions, + error?: Error, + expected: RegExp, ) => { - if (error && !error.message && !error.name) { - error = new Error(error); - } + const isDefined = error !== undefined; + const pass = isDefined && expected.test(error.message); - const pass = equals(error, expectedError); const message = pass ? () => - matcherHint('.not' + name, 'function', 'error') + + matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected the function not to throw an error matching:\n` + - ` ${printExpected(expectedError)}\n` + - printActualErrorMessage(error) + `Expected error pattern: ${printExpected(expected)}\n` + + // Possible improvement also for toMatch + // inverse highlight matching substring: + `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) : () => - matcherHint(name, 'function', 'error') + + matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected the function to throw an error matching:\n` + - ` ${printExpected(expectedError)}\n` + - printActualErrorMessage(error); + `Expected error pattern: ${printExpected(expected)}\n` + + (isDefined + ? `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) + : '\n' + DID_NOT_THROW); return {message, pass}; }; -const toThrowMatchingError = ( - name: string, - error: ?Error, - ErrorClass: typeof Error, +const toThrowExpectedObject = ( + matcherName: string, + options: MatcherHintOptions, + error?: Error, + expected: Object, ) => { - const pass = !!(error && error instanceof ErrorClass); + const isDefined = error !== undefined; + const pass = isDefined && error.message === expected.message; + const message = pass ? () => - matcherHint('.not' + name, 'function', 'type') + + matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected the function not to throw an error of type:\n` + - ` ${printExpected(ErrorClass.name)}\n` + - printActualErrorMessage(error) + `Expected error message: ${printReceived(expected.message)}\n` + + `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) : () => - matcherHint(name, 'function', 'type') + + matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected the function to throw an error of type:\n` + - ` ${printExpected(ErrorClass.name)}\n` + - printActualErrorMessage(error); + `Expected error message: ${printReceived(expected.message)}\n` + + (isDefined + ? `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) + : '\n' + DID_NOT_THROW); return {message, pass}; }; -const printActualErrorMessage = error => { - if (error) { - const {message, stack} = separateMessageFromStack(error.stack); - return ( - `Instead, it threw:\n` + - RECEIVED_COLOR( - ' ' + - highlightTrailingWhitespace(message) + - formatStackTrace( - stack, - { - rootDir: process.cwd(), - testMatch: [], - }, - { - noStackTrace: false, - }, - ), - ) - ); - } - - return `But it didn't throw anything.`; +const toThrowExpectedClass = ( + matcherName: string, + options: MatcherHintOptions, + error?: Error, + expected: typeof Error, +) => { + const isDefined = error !== undefined; + const pass = isDefined && error instanceof expected; + + const message = () => + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected error name: ${printExpected(expected.name)}\n` + + (isDefined + ? `Received error name: ${printReceived(error.name)}\n\n` + + `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) + : '\n' + DID_NOT_THROW); + + return {message, pass}; +}; + +const toThrow = ( + matcherName: string, + options: MatcherHintOptions, + error?: Error, +) => { + const pass = error !== undefined; + + const message = pass + ? () => + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + `Received error name: ${printReceived(error.name)}\n` + + `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) + : () => + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + DID_NOT_THROW; + + return {message, pass}; }; +const formatErrorStack = stack => + formatStackTrace( + separateMessageFromStack(stack).stack, + { + rootDir: process.cwd(), + testMatch: [], + }, + { + noStackTrace: false, + }, + ); + export default matchers; From f09ad0ae01bbda8e26ee293d3e84e3128cbbb04f Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Sun, 13 Jan 2019 17:48:39 -0500 Subject: [PATCH 02/14] Fix flow errors --- packages/expect/src/toThrowMatchers.js | 56 +++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/packages/expect/src/toThrowMatchers.js b/packages/expect/src/toThrowMatchers.js index fad9f57bdbe9..3cfa0aebae0c 100644 --- a/packages/expect/src/toThrowMatchers.js +++ b/packages/expect/src/toThrowMatchers.js @@ -7,7 +7,7 @@ * @flow */ -import type {MatchersObject} from 'types/Matchers'; +import type {MatcherHintOptions, MatchersObject} from 'types/Matchers'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import { @@ -24,7 +24,7 @@ import {isError} from './utils'; const DID_NOT_THROW = 'Received function did not throw an exception'; export const createMatcher = (matcherName: string, fromPromise?: boolean) => - function(received: Function, expected: string | Error | RegExp) { + function(received: Function, expected: any) { const options = { isNot: this.isNot, promise: this.promise, @@ -59,16 +59,15 @@ export const createMatcher = (matcherName: string, fromPromise?: boolean) => error = new Error(error); } - const expectedType = typeof expected; - if (expectedType === 'undefined') { + if (expected === undefined) { return toThrow(matcherName, options, error); - } else if (expectedType === 'function') { + } else if (typeof expected === 'function') { return toThrowExpectedClass(matcherName, options, error, expected); - } else if (expectedType === 'string') { + } else if (typeof expected === 'string') { return toThrowExpectedString(matcherName, options, error, expected); - } else if (expected && typeof expected.test === 'function') { + } else if (expected !== null && typeof expected.test === 'function') { return toThrowExpectedRegExp(matcherName, options, error, expected); - } else if (expected && expectedType === 'object') { + } else if (expected !== null && typeof expected === 'object') { return toThrowExpectedObject(matcherName, options, error, expected); } else { throw new Error( @@ -94,8 +93,7 @@ const toThrowExpectedString = ( error?: Error, expected: string, ) => { - const isDefined = error !== undefined; - const pass = isDefined && error.message.includes(expected); + const pass = error !== undefined && error.message.includes(expected); const message = pass ? () => @@ -104,13 +102,15 @@ const toThrowExpectedString = ( `Expected error pattern: ${printExpected(expected)}\n` + // Possible improvement also for toMatch // inverse highlight matching substring: + // $FlowFixMe: Cannot get error.message because property message is missing in undefined `Received error message: ${printReceived(error.message)}\n` + + // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined formatErrorStack(error.stack) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected error pattern: ${printExpected(expected)}\n` + - (isDefined + (error !== undefined ? `Received error message: ${printReceived(error.message)}\n` + formatErrorStack(error.stack) : '\n' + DID_NOT_THROW); @@ -124,8 +124,7 @@ const toThrowExpectedRegExp = ( error?: Error, expected: RegExp, ) => { - const isDefined = error !== undefined; - const pass = isDefined && expected.test(error.message); + const pass = error !== undefined && expected.test(error.message); const message = pass ? () => @@ -134,13 +133,15 @@ const toThrowExpectedRegExp = ( `Expected error pattern: ${printExpected(expected)}\n` + // Possible improvement also for toMatch // inverse highlight matching substring: + // $FlowFixMe: Cannot get error.message because property message is missing in undefined `Received error message: ${printReceived(error.message)}\n` + + // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined formatErrorStack(error.stack) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected error pattern: ${printExpected(expected)}\n` + - (isDefined + (error !== undefined ? `Received error message: ${printReceived(error.message)}\n` + formatErrorStack(error.stack) : '\n' + DID_NOT_THROW); @@ -154,21 +155,22 @@ const toThrowExpectedObject = ( error?: Error, expected: Object, ) => { - const isDefined = error !== undefined; - const pass = isDefined && error.message === expected.message; + const pass = error !== undefined && error.message === expected.message; const message = pass ? () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected error message: ${printReceived(expected.message)}\n` + + // $FlowFixMe: Cannot get error.message because property message is missing in undefined `Received error message: ${printReceived(error.message)}\n` + + // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined formatErrorStack(error.stack) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected error message: ${printReceived(expected.message)}\n` + - (isDefined + (error !== undefined ? `Received error message: ${printReceived(error.message)}\n` + formatErrorStack(error.stack) : '\n' + DID_NOT_THROW); @@ -182,14 +184,13 @@ const toThrowExpectedClass = ( error?: Error, expected: typeof Error, ) => { - const isDefined = error !== undefined; - const pass = isDefined && error instanceof expected; + const pass = error !== undefined && error instanceof expected; const message = () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected error name: ${printExpected(expected.name)}\n` + - (isDefined + (error !== undefined ? `Received error name: ${printReceived(error.name)}\n\n` + `Received error message: ${printReceived(error.message)}\n` + formatErrorStack(error.stack) @@ -205,17 +206,14 @@ const toThrow = ( ) => { const pass = error !== undefined; - const message = pass - ? () => - matcherHint(matcherName, undefined, '', options) + - '\n\n' + - `Received error name: ${printReceived(error.name)}\n` + + const message = () => + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + (error !== undefined + ? `Received error name: ${printReceived(error.name)}\n` + `Received error message: ${printReceived(error.message)}\n` + formatErrorStack(error.stack) - : () => - matcherHint(matcherName, undefined, '', options) + - '\n\n' + - DID_NOT_THROW; + : DID_NOT_THROW); return {message, pass}; }; From 9747691629bba714e2f7c9fed797b064b4788ed4 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Sun, 13 Jan 2019 18:12:47 -0500 Subject: [PATCH 03/14] Move new function to reduce noise in diff --- packages/expect/src/toThrowMatchers.js | 64 +++++++++++++------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/packages/expect/src/toThrowMatchers.js b/packages/expect/src/toThrowMatchers.js index 3cfa0aebae0c..c938e12cf8cf 100644 --- a/packages/expect/src/toThrowMatchers.js +++ b/packages/expect/src/toThrowMatchers.js @@ -87,37 +87,6 @@ const matchers: MatchersObject = { toThrowError: createMatcher('toThrowError'), }; -const toThrowExpectedString = ( - matcherName: string, - options: MatcherHintOptions, - error?: Error, - expected: string, -) => { - const pass = error !== undefined && error.message.includes(expected); - - const message = pass - ? () => - matcherHint(matcherName, undefined, undefined, options) + - '\n\n' + - `Expected error pattern: ${printExpected(expected)}\n` + - // Possible improvement also for toMatch - // inverse highlight matching substring: - // $FlowFixMe: Cannot get error.message because property message is missing in undefined - `Received error message: ${printReceived(error.message)}\n` + - // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined - formatErrorStack(error.stack) - : () => - matcherHint(matcherName, undefined, undefined, options) + - '\n\n' + - `Expected error pattern: ${printExpected(expected)}\n` + - (error !== undefined - ? `Received error message: ${printReceived(error.message)}\n` + - formatErrorStack(error.stack) - : '\n' + DID_NOT_THROW); - - return {message, pass}; -}; - const toThrowExpectedRegExp = ( matcherName: string, options: MatcherHintOptions, @@ -131,8 +100,7 @@ const toThrowExpectedRegExp = ( matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected error pattern: ${printExpected(expected)}\n` + - // Possible improvement also for toMatch - // inverse highlight matching substring: + // Possible improvement also for toMatch inverse highlight matching substring. // $FlowFixMe: Cannot get error.message because property message is missing in undefined `Received error message: ${printReceived(error.message)}\n` + // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined @@ -199,6 +167,36 @@ const toThrowExpectedClass = ( return {message, pass}; }; +const toThrowExpectedString = ( + matcherName: string, + options: MatcherHintOptions, + error?: Error, + expected: string, +) => { + const pass = error !== undefined && error.message.includes(expected); + + const message = pass + ? () => + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected error pattern: ${printExpected(expected)}\n` + + // Possible improvement also for toMatch inverse highlight matching substring. + // $FlowFixMe: Cannot get error.message because property message is missing in undefined + `Received error message: ${printReceived(error.message)}\n` + + // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined + formatErrorStack(error.stack) + : () => + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + `Expected error pattern: ${printExpected(expected)}\n` + + (error !== undefined + ? `Received error message: ${printReceived(error.message)}\n` + + formatErrorStack(error.stack) + : '\n' + DID_NOT_THROW); + + return {message, pass}; +}; + const toThrow = ( matcherName: string, options: MatcherHintOptions, From 2a4684010e26459706ff1094c61e9906ebddb0d6 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 14 Jan 2019 10:15:59 -0500 Subject: [PATCH 04/14] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 957cae835bdc..c815f65bd3fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - `[jest-config]` Allow % based configuration of `--max-workers` ([#7494](https://github.com/facebook/jest/pull/7494)) - `[jest-runner]` Instantiate the test environment class with the current `testPath` ([#7442](https://github.com/facebook/jest/pull/7442)) - `[jest-config]` Always resolve jest-environment-jsdom from jest-config ([#7476](https://github.com/facebook/jest/pull/7476)) +- `[expect]` Improve report when assertion fails, part 6 ([#7621](https://github.com/facebook/jest/pull/7621)) ### Fixes From 39daf2c15d509d70dca75f98b7b860c41f0d5785 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 14 Jan 2019 10:16:49 -0500 Subject: [PATCH 05/14] Convert ANSI codes in promise/async snapshots --- .../toThrowMatchers.test.js.snap | 44 +++++++++---------- .../src/__tests__/toThrowMatchers.test.js | 6 +-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap index a0e0d4d3c525..31102b9a8896 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap @@ -49,29 +49,29 @@ Expected has value: 111" `; exports[`.toThrow() promise/async throws if Error-like object is returned did not throw at all 1`] = ` -[Error: expect(received).rejects.toThrow() +"expect(received).rejects.toThrow() -Received function did not throw an exception] +Received function did not throw an exception" `; exports[`.toThrow() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` -[Error: expect(received).rejects.toThrow(expected) +"expect(received).rejects.toThrow(expected) -Expected error name: "Err2" -Received error name: "Error" +Expected error name: \\"Err2\\" +Received error name: \\"Error\\" -Received error message: "async apple" +Received error message: \\"async apple\\" - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` -[Error: expect(received).rejects.not.toThrow() +"expect(received).rejects.not.toThrow() -Received error name: "Error" -Received error message: "async apple" +Received error name: \\"Error\\" +Received error message: \\"async apple\\" - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrow() regexp did not throw at all 1`] = ` @@ -175,29 +175,29 @@ Expected has value: 111" `; exports[`.toThrowError() promise/async throws if Error-like object is returned did not throw at all 1`] = ` -[Error: expect(received).rejects.toThrowError() +"expect(received).rejects.toThrowError() -Received function did not throw an exception] +Received function did not throw an exception" `; exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` -[Error: expect(received).rejects.toThrowError(expected) +"expect(received).rejects.toThrowError(expected) -Expected error name: "Err2" -Received error name: "Error" +Expected error name: \\"Err2\\" +Received error name: \\"Error\\" -Received error message: "async apple" +Received error message: \\"async apple\\" - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` -[Error: expect(received).rejects.not.toThrowError() +"expect(received).rejects.not.toThrowError() -Received error name: "Error" -Received error message: "async apple" +Received error name: \\"Error\\" +Received error message: \\"async apple\\" - at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)] + at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; exports[`.toThrowError() regexp did not throw at all 1`] = ` diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.js b/packages/expect/src/__tests__/toThrowMatchers.test.js index fd7121fa55fa..29b942f95919 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.js +++ b/packages/expect/src/__tests__/toThrowMatchers.test.js @@ -205,7 +205,7 @@ class customError extends Error { } catch (error) { err = error; } - expect(err).toMatchSnapshot(); + expect(err && err.message).toMatchSnapshot(); }); test('threw, but class did not match', async () => { @@ -215,7 +215,7 @@ class customError extends Error { } catch (error) { err = error; } - expect(err).toMatchSnapshot(); + expect(err && err.message).toMatchSnapshot(); }); test('threw, but should not have', async () => { @@ -225,7 +225,7 @@ class customError extends Error { } catch (error) { err = error; } - expect(err).toMatchSnapshot(); + expect(err && err.message).toMatchSnapshot(); }); }); From fc766f12688d4825ce1400eeb1b551a6d9d36510 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Mon, 14 Jan 2019 14:25:06 -0500 Subject: [PATCH 06/14] Rewrite promise/async snapshot tests --- .../src/__tests__/toThrowMatchers.test.js | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.js b/packages/expect/src/__tests__/toThrowMatchers.test.js index 29b942f95919..3a8a6927bc83 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.js +++ b/packages/expect/src/__tests__/toThrowMatchers.test.js @@ -199,33 +199,21 @@ class customError extends Error { }); test('did not throw at all', async () => { - let err; - try { - await jestExpect(asyncFn()).rejects[toThrow](); - } catch (error) { - err = error; - } - expect(err && err.message).toMatchSnapshot(); + await expect( + jestExpect(asyncFn()).rejects[toThrow](), + ).rejects.toThrowErrorMatchingSnapshot(); }); test('threw, but class did not match', async () => { - let err; - try { - await jestExpect(asyncFn(true)).rejects[toThrow](Err2); - } catch (error) { - err = error; - } - expect(err && err.message).toMatchSnapshot(); + await expect( + jestExpect(asyncFn(true)).rejects[toThrow](Err2), + ).rejects.toThrowErrorMatchingSnapshot(); }); test('threw, but should not have', async () => { - let err; - try { - await jestExpect(asyncFn(true)).rejects.not[toThrow](); - } catch (error) { - err = error; - } - expect(err && err.message).toMatchSnapshot(); + await expect( + jestExpect(asyncFn(true)).rejects.not[toThrow](), + ).rejects.toThrowErrorMatchingSnapshot(); }); }); From 495db2f8ba0c14f59e2d3a33779d6513d4ee58c0 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 15:12:10 -0500 Subject: [PATCH 07/14] Reduce length of labels and handle non-error values --- .../toThrowMatchers.test.js.snap | 88 +++---- packages/expect/src/toThrowMatchers.js | 244 ++++++++++++------ 2 files changed, 205 insertions(+), 127 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap index 31102b9a8896..1d5cd098e92a 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap @@ -3,7 +3,7 @@ exports[`.toThrow() error class did not throw at all 1`] = ` "expect(received).toThrow(expected) -Expected error name: \\"Err\\" +Expected name: \\"Err\\" Received function did not throw an exception" `; @@ -11,10 +11,10 @@ Received function did not throw an exception" exports[`.toThrow() error class threw, but class did not match 1`] = ` "expect(received).toThrow(expected) -Expected error name: \\"Err2\\" -Received error name: \\"Error\\" +Expected name: \\"Err2\\" +Received name: \\"Error\\" -Received error message: \\"apple\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -22,10 +22,10 @@ Received error message: \\"apple\\" exports[`.toThrow() error class threw, but should not have 1`] = ` "expect(received).not.toThrow(expected) -Expected error name: \\"Err\\" -Received error name: \\"Error\\" +Expected name: \\"Err\\" +Received name: \\"Error\\" -Received error message: \\"apple\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -57,10 +57,10 @@ Received function did not throw an exception" exports[`.toThrow() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` "expect(received).rejects.toThrow(expected) -Expected error name: \\"Err2\\" -Received error name: \\"Error\\" +Expected name: \\"Err2\\" +Received name: \\"Error\\" -Received error message: \\"async apple\\" +Received message: \\"async apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -68,8 +68,8 @@ Received error message: \\"async apple\\" exports[`.toThrow() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` "expect(received).rejects.not.toThrow() -Received error name: \\"Error\\" -Received error message: \\"async apple\\" +Error name: \\"Error\\" +Error message: \\"async apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -77,7 +77,7 @@ Received error message: \\"async apple\\" exports[`.toThrow() regexp did not throw at all 1`] = ` "expect(received).toThrow(expected) -Expected error pattern: /apple/ +Expected pattern: /apple/ Received function did not throw an exception" `; @@ -85,8 +85,8 @@ Received function did not throw an exception" exports[`.toThrow() regexp threw, but message did not match 1`] = ` "expect(received).toThrow(expected) -Expected error pattern: /banana/ -Received error message: \\"apple\\" +Expected pattern: /banana/ +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -94,8 +94,8 @@ Received error message: \\"apple\\" exports[`.toThrow() regexp threw, but should not have 1`] = ` "expect(received).not.toThrow(expected) -Expected error pattern: /apple/ -Received error message: \\"apple\\" +Expected pattern: /apple/ +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -103,7 +103,7 @@ Received error message: \\"apple\\" exports[`.toThrow() strings did not throw at all 1`] = ` "expect(received).toThrow(expected) -Expected error pattern: \\"apple\\" +Expected substring: \\"apple\\" Received function did not throw an exception" `; @@ -111,8 +111,8 @@ Received function did not throw an exception" exports[`.toThrow() strings threw, but message did not match 1`] = ` "expect(received).toThrow(expected) -Expected error pattern: \\"banana\\" -Received error message: \\"apple\\" +Expected substring: \\"banana\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -120,8 +120,8 @@ Received error message: \\"apple\\" exports[`.toThrow() strings threw, but should not have 1`] = ` "expect(received).not.toThrow(expected) -Expected error pattern: \\"apple\\" -Received error message: \\"apple\\" +Expected substring: \\"apple\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -129,7 +129,7 @@ Received error message: \\"apple\\" exports[`.toThrowError() error class did not throw at all 1`] = ` "expect(received).toThrowError(expected) -Expected error name: \\"Err\\" +Expected name: \\"Err\\" Received function did not throw an exception" `; @@ -137,10 +137,10 @@ Received function did not throw an exception" exports[`.toThrowError() error class threw, but class did not match 1`] = ` "expect(received).toThrowError(expected) -Expected error name: \\"Err2\\" -Received error name: \\"Error\\" +Expected name: \\"Err2\\" +Received name: \\"Error\\" -Received error message: \\"apple\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -148,10 +148,10 @@ Received error message: \\"apple\\" exports[`.toThrowError() error class threw, but should not have 1`] = ` "expect(received).not.toThrowError(expected) -Expected error name: \\"Err\\" -Received error name: \\"Error\\" +Expected name: \\"Err\\" +Received name: \\"Error\\" -Received error message: \\"apple\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -183,10 +183,10 @@ Received function did not throw an exception" exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` "expect(received).rejects.toThrowError(expected) -Expected error name: \\"Err2\\" -Received error name: \\"Error\\" +Expected name: \\"Err2\\" +Received name: \\"Error\\" -Received error message: \\"async apple\\" +Received message: \\"async apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -194,8 +194,8 @@ Received error message: \\"async apple\\" exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but should not have 1`] = ` "expect(received).rejects.not.toThrowError() -Received error name: \\"Error\\" -Received error message: \\"async apple\\" +Error name: \\"Error\\" +Error message: \\"async apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -203,7 +203,7 @@ Received error message: \\"async apple\\" exports[`.toThrowError() regexp did not throw at all 1`] = ` "expect(received).toThrowError(expected) -Expected error pattern: /apple/ +Expected pattern: /apple/ Received function did not throw an exception" `; @@ -211,8 +211,8 @@ Received function did not throw an exception" exports[`.toThrowError() regexp threw, but message did not match 1`] = ` "expect(received).toThrowError(expected) -Expected error pattern: /banana/ -Received error message: \\"apple\\" +Expected pattern: /banana/ +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -220,8 +220,8 @@ Received error message: \\"apple\\" exports[`.toThrowError() regexp threw, but should not have 1`] = ` "expect(received).not.toThrowError(expected) -Expected error pattern: /apple/ -Received error message: \\"apple\\" +Expected pattern: /apple/ +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -229,7 +229,7 @@ Received error message: \\"apple\\" exports[`.toThrowError() strings did not throw at all 1`] = ` "expect(received).toThrowError(expected) -Expected error pattern: \\"apple\\" +Expected substring: \\"apple\\" Received function did not throw an exception" `; @@ -237,8 +237,8 @@ Received function did not throw an exception" exports[`.toThrowError() strings threw, but message did not match 1`] = ` "expect(received).toThrowError(expected) -Expected error pattern: \\"banana\\" -Received error message: \\"apple\\" +Expected substring: \\"banana\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; @@ -246,8 +246,8 @@ Received error message: \\"apple\\" exports[`.toThrowError() strings threw, but should not have 1`] = ` "expect(received).not.toThrowError(expected) -Expected error pattern: \\"apple\\" -Received error message: \\"apple\\" +Expected substring: \\"apple\\" +Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; diff --git a/packages/expect/src/toThrowMatchers.js b/packages/expect/src/toThrowMatchers.js index c938e12cf8cf..109aa45f673f 100644 --- a/packages/expect/src/toThrowMatchers.js +++ b/packages/expect/src/toThrowMatchers.js @@ -23,6 +23,35 @@ import {isError} from './utils'; const DID_NOT_THROW = 'Received function did not throw an exception'; +type Thrown = + | { + isError: true, + message: string, + value: Error, + } + | { + isError: false, + message: string, + value: any, + }; + +const getThrown = (e: any): Thrown => + e !== null && + e !== undefined && + typeof e.message === 'string' && + typeof e.name === 'string' && + typeof e.stack === 'string' + ? { + isError: true, + message: e.message, + value: e, + } + : { + isError: false, + message: typeof e === 'string' ? e : String(e), + value: e, + }; + export const createMatcher = (matcherName: string, fromPromise?: boolean) => function(received: Function, expected: any) { const options = { @@ -30,10 +59,10 @@ export const createMatcher = (matcherName: string, fromPromise?: boolean) => promise: this.promise, }; - let error; + let thrown = null; if (fromPromise && isError(received)) { - error = received; + thrown = getThrown(received); } else { if (typeof received !== 'function') { if (!fromPromise) { @@ -50,25 +79,21 @@ export const createMatcher = (matcherName: string, fromPromise?: boolean) => try { received(); } catch (e) { - error = e; + thrown = getThrown(e); } } } - if (error && !error.message && !error.name) { - error = new Error(error); - } - if (expected === undefined) { - return toThrow(matcherName, options, error); + return toThrow(matcherName, options, thrown); } else if (typeof expected === 'function') { - return toThrowExpectedClass(matcherName, options, error, expected); + return toThrowExpectedClass(matcherName, options, thrown, expected); } else if (typeof expected === 'string') { - return toThrowExpectedString(matcherName, options, error, expected); + return toThrowExpectedString(matcherName, options, thrown, expected); } else if (expected !== null && typeof expected.test === 'function') { - return toThrowExpectedRegExp(matcherName, options, error, expected); + return toThrowExpectedRegExp(matcherName, options, thrown, expected); } else if (expected !== null && typeof expected === 'object') { - return toThrowExpectedObject(matcherName, options, error, expected); + return toThrowExpectedObject(matcherName, options, thrown, expected); } else { throw new Error( matcherErrorMessage( @@ -90,29 +115,31 @@ const matchers: MatchersObject = { const toThrowExpectedRegExp = ( matcherName: string, options: MatcherHintOptions, - error?: Error, + thrown: Thrown | null, expected: RegExp, ) => { - const pass = error !== undefined && expected.test(error.message); + const pass = thrown !== null && expected.test(thrown.message); + const isNotError = thrown !== null && !thrown.isError; const message = pass ? () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected error pattern: ${printExpected(expected)}\n` + - // Possible improvement also for toMatch inverse highlight matching substring. - // $FlowFixMe: Cannot get error.message because property message is missing in undefined - `Received error message: ${printReceived(error.message)}\n` + - // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined - formatErrorStack(error.stack) + formatExpected('Expected pattern: ', expected) + + (isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected error pattern: ${printExpected(expected)}\n` + - (error !== undefined - ? `Received error message: ${printReceived(error.message)}\n` + - formatErrorStack(error.stack) - : '\n' + DID_NOT_THROW); + formatExpected('Expected pattern: ', expected) + + (thrown === null + ? '\n' + DID_NOT_THROW + : isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)); return {message, pass}; }; @@ -120,28 +147,31 @@ const toThrowExpectedRegExp = ( const toThrowExpectedObject = ( matcherName: string, options: MatcherHintOptions, - error?: Error, + thrown: Thrown | null, expected: Object, ) => { - const pass = error !== undefined && error.message === expected.message; + const pass = thrown !== null && thrown.message === expected.message; + const isNotError = thrown !== null && !thrown.isError; const message = pass ? () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected error message: ${printReceived(expected.message)}\n` + - // $FlowFixMe: Cannot get error.message because property message is missing in undefined - `Received error message: ${printReceived(error.message)}\n` + - // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined - formatErrorStack(error.stack) + formatExpected('Expected message: ', expected.message) + + (isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected error message: ${printReceived(expected.message)}\n` + - (error !== undefined - ? `Received error message: ${printReceived(error.message)}\n` + - formatErrorStack(error.stack) - : '\n' + DID_NOT_THROW); + formatExpected('Expected message: ', expected.message) + + (thrown === null + ? '\n' + DID_NOT_THROW + : isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)); return {message, pass}; }; @@ -149,20 +179,33 @@ const toThrowExpectedObject = ( const toThrowExpectedClass = ( matcherName: string, options: MatcherHintOptions, - error?: Error, + thrown: Thrown | null, expected: typeof Error, ) => { - const pass = error !== undefined && error instanceof expected; - - const message = () => - matcherHint(matcherName, undefined, undefined, options) + - '\n\n' + - `Expected error name: ${printExpected(expected.name)}\n` + - (error !== undefined - ? `Received error name: ${printReceived(error.name)}\n\n` + - `Received error message: ${printReceived(error.message)}\n` + - formatErrorStack(error.stack) - : '\n' + DID_NOT_THROW); + const pass = thrown !== null && thrown.value instanceof expected; + const isNotError = thrown !== null && !thrown.isError; + + const message = pass + ? () => + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected name: ', expected.name) + + formatReceived('Received name: ', thrown, 'name') + + '\n' + + formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown) + : () => + matcherHint(matcherName, undefined, undefined, options) + + '\n\n' + + formatExpected('Expected name: ', expected.name) + + (thrown === null + ? '\n' + DID_NOT_THROW + : isNotError + ? '\n' + formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received name: ', thrown, 'name') + + '\n' + + formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)); return {message, pass}; }; @@ -170,29 +213,31 @@ const toThrowExpectedClass = ( const toThrowExpectedString = ( matcherName: string, options: MatcherHintOptions, - error?: Error, + thrown: Thrown | null, expected: string, ) => { - const pass = error !== undefined && error.message.includes(expected); + const pass = thrown !== null && thrown.message.includes(expected); + const isNotError = thrown !== null && !thrown.isError; const message = pass ? () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected error pattern: ${printExpected(expected)}\n` + - // Possible improvement also for toMatch inverse highlight matching substring. - // $FlowFixMe: Cannot get error.message because property message is missing in undefined - `Received error message: ${printReceived(error.message)}\n` + - // $FlowFixMe: Cannot get error.stack because property stack is missing in undefined - formatErrorStack(error.stack) + formatExpected('Expected substring: ', expected) + + (isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - `Expected error pattern: ${printExpected(expected)}\n` + - (error !== undefined - ? `Received error message: ${printReceived(error.message)}\n` + - formatErrorStack(error.stack) - : '\n' + DID_NOT_THROW); + formatExpected('Expected substring: ', expected) + + (thrown === null + ? '\n' + DID_NOT_THROW + : isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)); return {message, pass}; }; @@ -200,32 +245,65 @@ const toThrowExpectedString = ( const toThrow = ( matcherName: string, options: MatcherHintOptions, - error?: Error, + thrown: Thrown | null, ) => { - const pass = error !== undefined; + const pass = thrown !== null; + const isNotError = thrown !== null && !thrown.isError; - const message = () => - matcherHint(matcherName, undefined, '', options) + - '\n\n' + - (error !== undefined - ? `Received error name: ${printReceived(error.name)}\n` + - `Received error message: ${printReceived(error.message)}\n` + - formatErrorStack(error.stack) - : DID_NOT_THROW); + const message = pass + ? () => + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + (isNotError + ? formatReceived('Thrown value: ', thrown, 'value') + : formatReceived('Error name: ', thrown, 'name') + + formatReceived('Error message: ', thrown, 'message') + + formatStack(thrown)) + : () => + matcherHint(matcherName, undefined, '', options) + + '\n\n' + + DID_NOT_THROW; return {message, pass}; }; -const formatErrorStack = stack => - formatStackTrace( - separateMessageFromStack(stack).stack, - { - rootDir: process.cwd(), - testMatch: [], - }, - { - noStackTrace: false, - }, - ); +const formatExpected = (label: string, expected: any) => + label + printExpected(expected) + '\n'; + +const formatReceived = (label: string, thrown: Thrown | null, key: string) => { + if (thrown === null) { + return ''; + } + + if (key === 'message') { + return label + printReceived(thrown.message) + '\n'; + } + + if (key === 'name') { + return thrown.isError + ? label + printReceived(thrown.value.name) + '\n' + : ''; + } + + if (key === 'value') { + return thrown.isError ? '' : label + printReceived(thrown.value) + '\n'; + } + + return ''; +}; + +const formatStack = (thrown: Thrown | null) => + thrown === null || !thrown.isError + ? '' + : formatStackTrace( + separateMessageFromStack(thrown.value.stack).stack, + { + rootDir: process.cwd(), + testMatch: [], + }, + { + noStackTrace: false, + }, + ); export default matchers; From 0c94113cf2d15171918f66cce8993617b7088f72 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 16:57:40 -0500 Subject: [PATCH 08/14] Add tests for non-error values --- .../toThrowMatchers.test.js.snap | 120 ++++++++++++++++-- .../src/__tests__/toThrowMatchers.test.js | 68 +++++++++- 2 files changed, 170 insertions(+), 18 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap index 1d5cd098e92a..3345d4eb677e 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap @@ -8,7 +8,7 @@ Expected name: \\"Err\\" Received function did not throw an exception" `; -exports[`.toThrow() error class threw, but class did not match 1`] = ` +exports[`.toThrow() error class threw, but class did not match (error) 1`] = ` "expect(received).toThrow(expected) Expected name: \\"Err2\\" @@ -19,7 +19,16 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; -exports[`.toThrow() error class threw, but should not have 1`] = ` +exports[`.toThrow() error class threw, but class did not match (non-error falsey) 1`] = ` +"expect(received).toThrow(expected) + +Expected name: \\"Err2\\" + +Received value: undefined +" +`; + +exports[`.toThrow() error class threw, but class should not match (error) 1`] = ` "expect(received).not.toThrow(expected) Expected name: \\"Err\\" @@ -30,6 +39,13 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; +exports[`.toThrow() expected is undefined threw, but should not have (non-error falsey) 1`] = ` +"expect(received).not.toThrow() + +Thrown value: null +" +`; + exports[`.toThrow() invalid actual 1`] = ` "expect(received).toThrow() @@ -82,7 +98,7 @@ Expected pattern: /apple/ Received function did not throw an exception" `; -exports[`.toThrow() regexp threw, but message did not match 1`] = ` +exports[`.toThrow() regexp threw, but message did not match (error) 1`] = ` "expect(received).toThrow(expected) Expected pattern: /banana/ @@ -91,7 +107,15 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; -exports[`.toThrow() regexp threw, but should not have 1`] = ` +exports[`.toThrow() regexp threw, but message did not match (non-error falsey) 1`] = ` +"expect(received).toThrow(expected) + +Expected pattern: /^[123456789]\\\\d*/ +Received value: 0 +" +`; + +exports[`.toThrow() regexp threw, but message should not match (error) 1`] = ` "expect(received).not.toThrow(expected) Expected pattern: /apple/ @@ -100,6 +124,14 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; +exports[`.toThrow() regexp threw, but message should not match (non-error truthy) 1`] = ` +"expect(received).not.toThrow(expected) + +Expected pattern: /^[123456789]\\\\d*/ +Received value: 404 +" +`; + exports[`.toThrow() strings did not throw at all 1`] = ` "expect(received).toThrow(expected) @@ -108,7 +140,7 @@ Expected substring: \\"apple\\" Received function did not throw an exception" `; -exports[`.toThrow() strings threw, but message did not match 1`] = ` +exports[`.toThrow() strings threw, but message did not match (error) 1`] = ` "expect(received).toThrow(expected) Expected substring: \\"banana\\" @@ -117,7 +149,15 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; -exports[`.toThrow() strings threw, but should not have 1`] = ` +exports[`.toThrow() strings threw, but message did not match (non-error falsey) 1`] = ` +"expect(received).toThrow(expected) + +Expected substring: \\"Server Error\\" +Received value: \\"\\" +" +`; + +exports[`.toThrow() strings threw, but message should not match (error) 1`] = ` "expect(received).not.toThrow(expected) Expected substring: \\"apple\\" @@ -126,6 +166,14 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; +exports[`.toThrow() strings threw, but message should not match (non-error truthy) 1`] = ` +"expect(received).not.toThrow(expected) + +Expected substring: \\"Server Error\\" +Received value: \\"Internal Server Error\\" +" +`; + exports[`.toThrowError() error class did not throw at all 1`] = ` "expect(received).toThrowError(expected) @@ -134,7 +182,7 @@ Expected name: \\"Err\\" Received function did not throw an exception" `; -exports[`.toThrowError() error class threw, but class did not match 1`] = ` +exports[`.toThrowError() error class threw, but class did not match (error) 1`] = ` "expect(received).toThrowError(expected) Expected name: \\"Err2\\" @@ -145,7 +193,16 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; -exports[`.toThrowError() error class threw, but should not have 1`] = ` +exports[`.toThrowError() error class threw, but class did not match (non-error falsey) 1`] = ` +"expect(received).toThrowError(expected) + +Expected name: \\"Err2\\" + +Received value: undefined +" +`; + +exports[`.toThrowError() error class threw, but class should not match (error) 1`] = ` "expect(received).not.toThrowError(expected) Expected name: \\"Err\\" @@ -156,6 +213,13 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; +exports[`.toThrowError() expected is undefined threw, but should not have (non-error falsey) 1`] = ` +"expect(received).not.toThrowError() + +Thrown value: null +" +`; + exports[`.toThrowError() invalid actual 1`] = ` "expect(received).toThrowError() @@ -208,7 +272,7 @@ Expected pattern: /apple/ Received function did not throw an exception" `; -exports[`.toThrowError() regexp threw, but message did not match 1`] = ` +exports[`.toThrowError() regexp threw, but message did not match (error) 1`] = ` "expect(received).toThrowError(expected) Expected pattern: /banana/ @@ -217,7 +281,15 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; -exports[`.toThrowError() regexp threw, but should not have 1`] = ` +exports[`.toThrowError() regexp threw, but message did not match (non-error falsey) 1`] = ` +"expect(received).toThrowError(expected) + +Expected pattern: /^[123456789]\\\\d*/ +Received value: 0 +" +`; + +exports[`.toThrowError() regexp threw, but message should not match (error) 1`] = ` "expect(received).not.toThrowError(expected) Expected pattern: /apple/ @@ -226,6 +298,14 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; +exports[`.toThrowError() regexp threw, but message should not match (non-error truthy) 1`] = ` +"expect(received).not.toThrowError(expected) + +Expected pattern: /^[123456789]\\\\d*/ +Received value: 404 +" +`; + exports[`.toThrowError() strings did not throw at all 1`] = ` "expect(received).toThrowError(expected) @@ -234,7 +314,7 @@ Expected substring: \\"apple\\" Received function did not throw an exception" `; -exports[`.toThrowError() strings threw, but message did not match 1`] = ` +exports[`.toThrowError() strings threw, but message did not match (error) 1`] = ` "expect(received).toThrowError(expected) Expected substring: \\"banana\\" @@ -243,7 +323,15 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; -exports[`.toThrowError() strings threw, but should not have 1`] = ` +exports[`.toThrowError() strings threw, but message did not match (non-error falsey) 1`] = ` +"expect(received).toThrowError(expected) + +Expected substring: \\"Server Error\\" +Received value: \\"\\" +" +`; + +exports[`.toThrowError() strings threw, but message should not match (error) 1`] = ` "expect(received).not.toThrowError(expected) Expected substring: \\"apple\\" @@ -251,3 +339,11 @@ Received message: \\"apple\\" at jestExpect (packages/expect/src/__tests__/toThrowMatchers-test.js:24:74)" `; + +exports[`.toThrowError() strings threw, but message should not match (non-error truthy) 1`] = ` +"expect(received).not.toThrowError(expected) + +Expected substring: \\"Server Error\\" +Received value: \\"Internal Server Error\\" +" +`; diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.js b/packages/expect/src/__tests__/toThrowMatchers.test.js index 3a8a6927bc83..b156b2c9e810 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.js +++ b/packages/expect/src/__tests__/toThrowMatchers.test.js @@ -52,7 +52,7 @@ class customError extends Error { ).toThrowErrorMatchingSnapshot(); }); - test('threw, but message did not match', () => { + test('threw, but message did not match (error)', () => { expect(() => { jestExpect(() => { throw new customError('apple'); @@ -60,19 +60,37 @@ class customError extends Error { }).toThrowErrorMatchingSnapshot(); }); + test('threw, but message did not match (non-error falsey)', () => { + expect(() => { + jestExpect(() => { + // eslint-disable-next-line no-throw-literal + throw ''; + })[toThrow]('Server Error'); + }).toThrowErrorMatchingSnapshot(); + }); + it('properly escapes strings when matching against errors', () => { jestExpect(() => { throw new TypeError('"this"? throws.'); })[toThrow]('"this"? throws.'); }); - test('threw, but should not have', () => { + test('threw, but message should not match (error)', () => { expect(() => { jestExpect(() => { throw new customError('apple'); }).not[toThrow]('apple'); }).toThrowErrorMatchingSnapshot(); }); + + test('threw, but message should not match (non-error truthy)', () => { + expect(() => { + jestExpect(() => { + // eslint-disable-next-line no-throw-literal + throw 'Internal Server Error'; + }).not[toThrow]('Server Error'); + }).toThrowErrorMatchingSnapshot(); + }); }); describe('regexp', () => { @@ -92,7 +110,7 @@ class customError extends Error { ).toThrowErrorMatchingSnapshot(); }); - test('threw, but message did not match', () => { + test('threw, but message did not match (error)', () => { expect(() => { jestExpect(() => { throw new customError('apple'); @@ -100,13 +118,31 @@ class customError extends Error { }).toThrowErrorMatchingSnapshot(); }); - test('threw, but should not have', () => { + test('threw, but message did not match (non-error falsey)', () => { + expect(() => { + jestExpect(() => { + // eslint-disable-next-line no-throw-literal + throw 0; + })[toThrow](/^[123456789]\d*/); + }).toThrowErrorMatchingSnapshot(); + }); + + test('threw, but message should not match (error)', () => { expect(() => { jestExpect(() => { throw new customError('apple'); }).not[toThrow](/apple/); }).toThrowErrorMatchingSnapshot(); }); + + test('threw, but message should not match (non-error truthy)', () => { + expect(() => { + jestExpect(() => { + // eslint-disable-next-line no-throw-literal + throw 404; + }).not[toThrow](/^[123456789]\d*/); + }).toThrowErrorMatchingSnapshot(); + }); }); describe('error class', () => { @@ -129,7 +165,7 @@ class customError extends Error { ).toThrowErrorMatchingSnapshot(); }); - test('threw, but class did not match', () => { + test('threw, but class did not match (error)', () => { expect(() => { jestExpect(() => { throw new Err('apple'); @@ -137,7 +173,16 @@ class customError extends Error { }).toThrowErrorMatchingSnapshot(); }); - test('threw, but should not have', () => { + test('threw, but class did not match (non-error falsey)', () => { + expect(() => { + jestExpect(() => { + // eslint-disable-next-line no-throw-literal + throw undefined; + })[toThrow](Err2); + }).toThrowErrorMatchingSnapshot(); + }); + + test('threw, but class should not match (error)', () => { expect(() => { jestExpect(() => { throw new Err('apple'); @@ -217,6 +262,17 @@ class customError extends Error { }); }); + describe('expected is undefined', () => { + test('threw, but should not have (non-error falsey)', () => { + expect(() => { + jestExpect(() => { + // eslint-disable-next-line no-throw-literal + throw null; + }).not[toThrow](); + }).toThrowErrorMatchingSnapshot(); + }) + }); + test('invalid arguments', () => { expect(() => jestExpect(() => {}).not[toThrow](111), From 44118a3413a35e00e94c9e6a99c7577b127b3702 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 17:21:21 -0500 Subject: [PATCH 09/14] Fix prettier lint error --- packages/expect/src/__tests__/toThrowMatchers.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.js b/packages/expect/src/__tests__/toThrowMatchers.test.js index b156b2c9e810..251cdcd44c81 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.js +++ b/packages/expect/src/__tests__/toThrowMatchers.test.js @@ -270,7 +270,7 @@ class customError extends Error { throw null; }).not[toThrow](); }).toThrowErrorMatchingSnapshot(); - }) + }); }); test('invalid arguments', () => { From 6d8a416f73f33de364904fc420aeb17e196425b8 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 17:29:08 -0500 Subject: [PATCH 10/14] Update ExpectAPI.md --- docs/ExpectAPI.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index f9c9278be63f..4207d90e3659 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1168,7 +1168,14 @@ test('throws on octopus', () => { }); ``` -If you want to test that a specific error gets thrown, you can provide an argument to `toThrow`. The argument can be a string that should be contained in the error message, a class for the error, or a regex that should match the error message. For example, let's say that `drinkFlavor` is coded like this: +If you want to test that a specific error gets thrown, you can provide an argument to `toThrow` + +- regular expression: error message **matches** the pattern +- string: error message **includes** the substring +- error object: error message is **equal to** the message property of the object +- error class: error object is **instance of** class + +For example, let's say that `drinkFlavor` is coded like this: ```js function drinkFlavor(flavor) { @@ -1193,6 +1200,7 @@ test('throws on octopus', () => { // Test the exact error message expect(drinkOctopus).toThrowError(/^yuck, octopus flavor$/); + expect(drinkOctopus).toThrowError(new Error('yuck, octopus flavor')); // Test that we get a DisgustingFlavorError expect(drinkOctopus).toThrowError(DisgustingFlavorError); From d576e721cf4d205029915ed9fd54875c62cadb82 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 18:11:37 -0500 Subject: [PATCH 11/14] Improve grammar in ExpectAPI.md --- docs/ExpectAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 4207d90e3659..2833a6c87c7f 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1168,7 +1168,7 @@ test('throws on octopus', () => { }); ``` -If you want to test that a specific error gets thrown, you can provide an argument to `toThrow` +To test that a specific error it thrown, you can provide an argument: - regular expression: error message **matches** the pattern - string: error message **includes** the substring From 4218be0c056b157ec3ac2b38efe0fef92e491991 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 18:26:16 -0500 Subject: [PATCH 12/14] Delete an exception from Received function did not throw --- .../__snapshots__/toThrowMatchers.test.js.snap | 16 ++++++++-------- packages/expect/src/toThrowMatchers.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap index 3345d4eb677e..2d2df6b246e0 100644 --- a/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap +++ b/packages/expect/src/__tests__/__snapshots__/toThrowMatchers.test.js.snap @@ -5,7 +5,7 @@ exports[`.toThrow() error class did not throw at all 1`] = ` Expected name: \\"Err\\" -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrow() error class threw, but class did not match (error) 1`] = ` @@ -67,7 +67,7 @@ Expected has value: 111" exports[`.toThrow() promise/async throws if Error-like object is returned did not throw at all 1`] = ` "expect(received).rejects.toThrow() -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrow() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` @@ -95,7 +95,7 @@ exports[`.toThrow() regexp did not throw at all 1`] = ` Expected pattern: /apple/ -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrow() regexp threw, but message did not match (error) 1`] = ` @@ -137,7 +137,7 @@ exports[`.toThrow() strings did not throw at all 1`] = ` Expected substring: \\"apple\\" -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrow() strings threw, but message did not match (error) 1`] = ` @@ -179,7 +179,7 @@ exports[`.toThrowError() error class did not throw at all 1`] = ` Expected name: \\"Err\\" -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrowError() error class threw, but class did not match (error) 1`] = ` @@ -241,7 +241,7 @@ Expected has value: 111" exports[`.toThrowError() promise/async throws if Error-like object is returned did not throw at all 1`] = ` "expect(received).rejects.toThrowError() -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrowError() promise/async throws if Error-like object is returned threw, but class did not match 1`] = ` @@ -269,7 +269,7 @@ exports[`.toThrowError() regexp did not throw at all 1`] = ` Expected pattern: /apple/ -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrowError() regexp threw, but message did not match (error) 1`] = ` @@ -311,7 +311,7 @@ exports[`.toThrowError() strings did not throw at all 1`] = ` Expected substring: \\"apple\\" -Received function did not throw an exception" +Received function did not throw" `; exports[`.toThrowError() strings threw, but message did not match (error) 1`] = ` diff --git a/packages/expect/src/toThrowMatchers.js b/packages/expect/src/toThrowMatchers.js index 109aa45f673f..7bb48efa5e53 100644 --- a/packages/expect/src/toThrowMatchers.js +++ b/packages/expect/src/toThrowMatchers.js @@ -21,7 +21,7 @@ import { } from 'jest-matcher-utils'; import {isError} from './utils'; -const DID_NOT_THROW = 'Received function did not throw an exception'; +const DID_NOT_THROW = 'Received function did not throw'; type Thrown = | { From 4ea80449cfdb825086d594c4457818aee9da574f Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Wed, 16 Jan 2019 18:29:09 -0500 Subject: [PATCH 13/14] Correct typo in ExpectAPI.md --- docs/ExpectAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 2833a6c87c7f..81c79eae95d1 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -1168,7 +1168,7 @@ test('throws on octopus', () => { }); ``` -To test that a specific error it thrown, you can provide an argument: +To test that a specific error is thrown, you can provide an argument: - regular expression: error message **matches** the pattern - string: error message **includes** the substring From 2a3f1c86aef2e7b61ba778be26906e938a0fb844 Mon Sep 17 00:00:00 2001 From: Mark Pedrotti Date: Thu, 17 Jan 2019 10:34:21 -0500 Subject: [PATCH 14/14] Correct type and report for non-error class --- packages/expect/src/toThrowMatchers.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/toThrowMatchers.js b/packages/expect/src/toThrowMatchers.js index 7bb48efa5e53..1dfd4f27fdf6 100644 --- a/packages/expect/src/toThrowMatchers.js +++ b/packages/expect/src/toThrowMatchers.js @@ -180,7 +180,7 @@ const toThrowExpectedClass = ( matcherName: string, options: MatcherHintOptions, thrown: Thrown | null, - expected: typeof Error, + expected: Function, ) => { const pass = thrown !== null && thrown.value instanceof expected; const isNotError = thrown !== null && !thrown.isError; @@ -192,8 +192,10 @@ const toThrowExpectedClass = ( formatExpected('Expected name: ', expected.name) + formatReceived('Received name: ', thrown, 'name') + '\n' + - formatReceived('Received message: ', thrown, 'message') + - formatStack(thrown) + (isNotError + ? formatReceived('Received value: ', thrown, 'value') + : formatReceived('Received message: ', thrown, 'message') + + formatStack(thrown)) : () => matcherHint(matcherName, undefined, undefined, options) + '\n\n' +