From e062068ce859d08220f6fdfa6740fc98c67079af Mon Sep 17 00:00:00 2001 From: Dirk-Jan Rutten Date: Sun, 5 Mar 2017 11:22:07 +0100 Subject: [PATCH 1/4] Adds promise support to all the matchers --- .../__snapshots__/matchers-test.js.snap | 138 ++++++++++++++++++ .../src/__tests__/matchers-test.js | 85 +++++++++++ packages/jest-matchers/src/index.js | 91 +++++++++++- types/Matchers.js | 9 ++ 4 files changed, 322 insertions(+), 1 deletion(-) diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index b9d2dd73753b..4d01bad5dbcb 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -1,5 +1,143 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`.rejects fails for promise that resolves 1`] = ` +"expect(received).rejects.toBe() + +Expected received Promise to reject, instead it resolved to value + 4" +`; + +exports[`.rejects fails non-promise value "a" 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: + string: \\"a\\"" +`; + +exports[`.rejects fails non-promise value [1] 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: + array: [1]" +`; + +exports[`.rejects fails non-promise value [Function anonymous] 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: + function: [Function anonymous]" +`; + +exports[`.rejects fails non-promise value {"a": 1} 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: + object: {\\"a\\": 1}" +`; + +exports[`.rejects fails non-promise value 4 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: + number: 4" +`; + +exports[`.rejects fails non-promise value null 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: null" +`; + +exports[`.rejects fails non-promise value true 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: + boolean: true" +`; + +exports[`.rejects fails non-promise value undefined 1`] = ` +"expect(received).rejects.toBeDefined() + +received value must be a Promise. +Received: undefined" +`; + +exports[`.resolves fails for promise that rejects 1`] = ` +"expect(received).resolves.toBe() + +Expected received Promise to resolve, instead it rejected to value + undefined" +`; + +exports[`.resolves fails non-promise value "a" 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: + string: \\"a\\"" +`; + +exports[`.resolves fails non-promise value [1] 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: + array: [1]" +`; + +exports[`.resolves fails non-promise value [Function anonymous] 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: + function: [Function anonymous]" +`; + +exports[`.resolves fails non-promise value {"a": 1} 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: + object: {\\"a\\": 1}" +`; + +exports[`.resolves fails non-promise value 4 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: + number: 4" +`; + +exports[`.resolves fails non-promise value null 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: null" +`; + +exports[`.resolves fails non-promise value true 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: + boolean: true" +`; + +exports[`.resolves fails non-promise value undefined 1`] = ` +"expect(received).resolves.toBeDefined() + +received value must be a Promise. +Received: undefined" +`; + exports[`.toBe() does not crash on circular references 1`] = ` "expect(received).toBe(expected) diff --git a/packages/jest-matchers/src/__tests__/matchers-test.js b/packages/jest-matchers/src/__tests__/matchers-test.js index 525d2783b2ba..1aa750534959 100644 --- a/packages/jest-matchers/src/__tests__/matchers-test.js +++ b/packages/jest-matchers/src/__tests__/matchers-test.js @@ -14,6 +14,91 @@ const jestExpect = require('../'); const {stringify} = require('jest-matcher-utils'); +describe('.rejects', () => { + it('should reject', async () => { + await jestExpect(Promise.reject(4)).rejects.toBe(4); + await jestExpect(Promise.reject(4)).rejects.not.toBe(5); + await jestExpect(Promise.reject({a: 1, b: 2})).rejects.toMatchObject({a: 1}); + await jestExpect(Promise.reject({a: 1, b: 2})).rejects.not.toMatchObject({c: 1}); + await jestExpect(Promise.reject(() => {throw new Error();})).rejects.toThrow(); + }); + + [ + 4, + [1], + {a: 1}, + 'a', + true, + null, + undefined, + () => {}, + ].forEach(value => { + it(`fails non-promise value ${stringify(value)}`, async () => { + let error; + try { + await jestExpect(value).rejects.toBeDefined(); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); + }); + + it('fails for promise that resolves', async () => { + let error; + try { + await jestExpect(Promise.resolve(4)).rejects.toBe(4); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); +}); + +describe('.resolves', () => { + it('should resolve', async () => { + await jestExpect(Promise.resolve(4)).resolves.toBe(4); + await jestExpect(Promise.resolve(4)).resolves.not.toBe(5); + await jestExpect(Promise.resolve({a: 1, b: 2})).resolves.toMatchObject({a: 1}); + await jestExpect(Promise.resolve({a: 1, b: 2})).resolves.not.toMatchObject({c: 1}); + await jestExpect(Promise.resolve(() => {throw new Error();})).resolves.toThrow(); + }); + + [ + 4, + [1], + {a: 1}, + 'a', + true, + null, + undefined, + () => {}, + ].forEach(value => { + it(`fails non-promise value ${stringify(value)}`, async () => { + let error; + try { + await jestExpect(value).resolves.toBeDefined(); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); + }); + + it('fails for promise that rejects', async () => { + let error; + try { + await jestExpect(Promise.reject(4)).resolves.toBe(4); + } catch (e) { + error = e; + } + expect(error).toBeDefined(); + expect(error.message).toMatchSnapshot(); + }); +}); describe('.toBe()', () => { it('does not throw', () => { jestExpect('a').not.toBe('b'); diff --git a/packages/jest-matchers/src/index.js b/packages/jest-matchers/src/index.js index 915135e6a7ef..2fea261237d5 100644 --- a/packages/jest-matchers/src/index.js +++ b/packages/jest-matchers/src/index.js @@ -19,6 +19,7 @@ import type { MatchersObject, RawMatcherFn, ThrowingMatcherFn, + PromiseMatcherFn, } from 'types/Matchers'; const matchers = require('./matchers'); @@ -41,6 +42,12 @@ class JestAssertionError extends Error { matcherResult: any; } +const isPromise = obj => { + return !!obj && + (typeof obj === 'object' || typeof obj === 'function') && + typeof obj.then === 'function'; +}; + if (!global[GLOBAL_STATE]) { Object.defineProperty(global, GLOBAL_STATE, { value: { @@ -56,7 +63,12 @@ if (!global[GLOBAL_STATE]) { const expect: Expect = (actual: any): ExpectationObject => { const allMatchers = global[GLOBAL_STATE].matchers; - const expectation = {not: {}}; + const expectation = { + not: {}, + rejects: {not: {}}, + resolves: {not: {}}, + }; + Object.keys(allMatchers).forEach(name => { expectation[name] = makeThrowingMatcher(allMatchers[name], false, actual); expectation.not[name] = makeThrowingMatcher( @@ -66,6 +78,24 @@ const expect: Expect = (actual: any): ExpectationObject => { ); }); + const resolveExpectation = {not: {}}; + Object.keys(allMatchers).forEach(name => { + resolveExpectation[name] = + makeResolveMatcher(name, allMatchers[name], false, actual); + resolveExpectation.not[name] = + makeResolveMatcher(name, allMatchers[name], true, actual); + }); + expectation.resolves = resolveExpectation; + + const rejectExpectation = {not: {}}; + Object.keys(allMatchers).forEach(name => { + rejectExpectation[name] = + makeRejectMatcher(name, allMatchers[name], false, actual); + rejectExpectation.not[name] = + makeRejectMatcher(name, allMatchers[name], true, actual); + }); + expectation.rejects = rejectExpectation; + return expectation; }; @@ -84,6 +114,65 @@ const getMessage = message => { return message; }; +const makeResolveMatcher = ( + matcherName: string, + matcher: RawMatcherFn, + isNot: boolean, + actual: Promise +): PromiseMatcherFn => async (...args) => { + const matcherStatement = `.resolves.${isNot ? 'not.' : ''}${matcherName}`; + if (!isPromise(actual)) { + throw new JestAssertionError( + utils.matcherHint(matcherStatement, 'received', '') + '\n\n' + + `${utils.RECEIVED_COLOR('received')} value must be a Promise.\n` + + utils.printWithType('Received', actual, utils.printReceived), + ); + } + + let result; + try { + result = await actual; + } catch (e) { + throw new JestAssertionError( + utils.matcherHint(matcherStatement, 'received', '') + '\n\n' + + `Expected ${utils.RECEIVED_COLOR('received')} Promise to resolve, ` + + 'instead it rejected to value\n' + + ` ${utils.printReceived(result)}` + ); + } + return makeThrowingMatcher(matcher, isNot, result)(...args); +}; + +const makeRejectMatcher = ( + matcherName: string, + matcher: RawMatcherFn, + isNot: boolean, + actual: Promise +): PromiseMatcherFn => async (...args) => { + const matcherStatement = `.rejects.${isNot ? 'not.' : ''}${matcherName}`; + if (!isPromise(actual)) { + throw new JestAssertionError( + utils.matcherHint(matcherStatement, 'received', '') + '\n\n' + + `${utils.RECEIVED_COLOR('received')} value must be a Promise.\n` + + utils.printWithType('Received', actual, utils.printReceived), + ); + } + + let result; + try { + result = await actual; + } catch (e) { + return makeThrowingMatcher(matcher, isNot, e)(...args); + } + + throw new JestAssertionError( + utils.matcherHint(matcherStatement, 'received', '') + '\n\n' + + `Expected ${utils.RECEIVED_COLOR('received')} Promise to reject, ` + + 'instead it resolved to value\n' + + ` ${utils.printReceived(result)}` + ); +}; + const makeThrowingMatcher = ( matcher: RawMatcherFn, isNot: boolean, diff --git a/types/Matchers.js b/types/Matchers.js index 8c0d14c684ac..7bc8d43f7a3a 100644 --- a/types/Matchers.js +++ b/types/Matchers.js @@ -23,6 +23,7 @@ export type RawMatcherFn = ( ) => ExpectationResult; export type ThrowingMatcherFn = (actual: any) => void; +export type PromiseMatcherFn = (actual: any) => Promise; export type MatcherContext = {isNot: boolean}; export type MatcherState = { assertionCalls?: number, @@ -33,6 +34,14 @@ export type MatcherState = { export type MatchersObject = {[id:string]: RawMatcherFn}; export type Expect = (expected: any) => ExpectationObject; export type ExpectationObject = { + resolves: { + [id: string]: PromiseMatcherFn, + not: {[id: string]: PromiseMatcherFn}, + }, + rejects: { + [id: string]: PromiseMatcherFn, + not: {[id: string]: PromiseMatcherFn}, + }, [id: string]: ThrowingMatcherFn, not: {[id: string]: ThrowingMatcherFn}, }; From d8b4ca470d2b5552fd18bf83083f2e311933302e Mon Sep 17 00:00:00 2001 From: Dirk-Jan Rutten Date: Wed, 8 Mar 2017 21:21:31 +0100 Subject: [PATCH 2/4] avoid duplicate iteration over all the matchers --- packages/jest-matchers/src/index.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/jest-matchers/src/index.js b/packages/jest-matchers/src/index.js index 2fea261237d5..acacbc4a350a 100644 --- a/packages/jest-matchers/src/index.js +++ b/packages/jest-matchers/src/index.js @@ -76,25 +76,17 @@ const expect: Expect = (actual: any): ExpectationObject => { true, actual, ); - }); - const resolveExpectation = {not: {}}; - Object.keys(allMatchers).forEach(name => { - resolveExpectation[name] = + expectation.resolves[name] = makeResolveMatcher(name, allMatchers[name], false, actual); - resolveExpectation.not[name] = + expectation.resolves.not[name] = makeResolveMatcher(name, allMatchers[name], true, actual); - }); - expectation.resolves = resolveExpectation; - const rejectExpectation = {not: {}}; - Object.keys(allMatchers).forEach(name => { - rejectExpectation[name] = + expectation.rejects[name] = makeRejectMatcher(name, allMatchers[name], false, actual); - rejectExpectation.not[name] = + expectation.rejects.not[name] = makeRejectMatcher(name, allMatchers[name], true, actual); }); - expectation.rejects = rejectExpectation; return expectation; }; From ffa5ab65c37976e1bea52bfe12765f2b2d392e69 Mon Sep 17 00:00:00 2001 From: Dirk-Jan Rutten Date: Sun, 12 Mar 2017 08:55:15 +0100 Subject: [PATCH 3/4] Fixed bug in spread operator by replacing it with an apply statement --- packages/jest-matchers/src/__tests__/matchers-test.js | 4 ++++ packages/jest-matchers/src/index.js | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/jest-matchers/src/__tests__/matchers-test.js b/packages/jest-matchers/src/__tests__/matchers-test.js index 1aa750534959..cc39a408e013 100644 --- a/packages/jest-matchers/src/__tests__/matchers-test.js +++ b/packages/jest-matchers/src/__tests__/matchers-test.js @@ -18,6 +18,8 @@ describe('.rejects', () => { it('should reject', async () => { await jestExpect(Promise.reject(4)).rejects.toBe(4); await jestExpect(Promise.reject(4)).rejects.not.toBe(5); + await jestExpect(Promise.reject(4.2)).rejects.toBeCloseTo(4.2, 5); + await jestExpect(Promise.reject((3))).rejects.not.toBeCloseTo(4.2, 5); await jestExpect(Promise.reject({a: 1, b: 2})).rejects.toMatchObject({a: 1}); await jestExpect(Promise.reject({a: 1, b: 2})).rejects.not.toMatchObject({c: 1}); await jestExpect(Promise.reject(() => {throw new Error();})).rejects.toThrow(); @@ -61,6 +63,8 @@ describe('.resolves', () => { it('should resolve', async () => { await jestExpect(Promise.resolve(4)).resolves.toBe(4); await jestExpect(Promise.resolve(4)).resolves.not.toBe(5); + await jestExpect(Promise.resolve(4.2)).resolves.toBeCloseTo(4.2, 5); + await jestExpect(Promise.resolve((3))).resolves.not.toBeCloseTo(4.2, 5); await jestExpect(Promise.resolve({a: 1, b: 2})).resolves.toMatchObject({a: 1}); await jestExpect(Promise.resolve({a: 1, b: 2})).resolves.not.toMatchObject({c: 1}); await jestExpect(Promise.resolve(() => {throw new Error();})).resolves.toThrow(); diff --git a/packages/jest-matchers/src/index.js b/packages/jest-matchers/src/index.js index acacbc4a350a..994d1cb1f718 100644 --- a/packages/jest-matchers/src/index.js +++ b/packages/jest-matchers/src/index.js @@ -132,7 +132,7 @@ const makeResolveMatcher = ( ` ${utils.printReceived(result)}` ); } - return makeThrowingMatcher(matcher, isNot, result)(...args); + return makeThrowingMatcher(matcher, isNot, result).apply(null, args); }; const makeRejectMatcher = ( @@ -154,7 +154,7 @@ const makeRejectMatcher = ( try { result = await actual; } catch (e) { - return makeThrowingMatcher(matcher, isNot, e)(...args); + return makeThrowingMatcher(matcher, isNot, e).apply(null, args); } throw new JestAssertionError( From 6e4a1934cc0085fc87d417b10cbac7114a85a874 Mon Sep 17 00:00:00 2001 From: Dirk-Jan Rutten Date: Sun, 12 Mar 2017 10:33:25 +0100 Subject: [PATCH 4/4] Updated snapshots to new format. --- .../__snapshots__/matchers-test.js.snap | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap index 4d01bad5dbcb..c25835ba7a79 100644 --- a/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap +++ b/packages/jest-matchers/src/__tests__/__snapshots__/matchers-test.js.snap @@ -1,141 +1,141 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`.rejects fails for promise that resolves 1`] = ` -"expect(received).rejects.toBe() +"expect(received).rejects.toBe() -Expected received Promise to reject, instead it resolved to value - 4" +Expected received Promise to reject, instead it resolved to value + 4" `; exports[`.rejects fails non-promise value "a" 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - string: \\"a\\"" + string: \\"a\\"" `; exports[`.rejects fails non-promise value [1] 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - array: [1]" + array: [1]" `; exports[`.rejects fails non-promise value [Function anonymous] 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - function: [Function anonymous]" + function: [Function anonymous]" `; exports[`.rejects fails non-promise value {"a": 1} 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - object: {\\"a\\": 1}" + object: {\\"a\\": 1}" `; exports[`.rejects fails non-promise value 4 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - number: 4" + number: 4" `; exports[`.rejects fails non-promise value null 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. -Received: null" +received value must be a Promise. +Received: null" `; exports[`.rejects fails non-promise value true 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - boolean: true" + boolean: true" `; exports[`.rejects fails non-promise value undefined 1`] = ` -"expect(received).rejects.toBeDefined() +"expect(received).rejects.toBeDefined() -received value must be a Promise. -Received: undefined" +received value must be a Promise. +Received: undefined" `; exports[`.resolves fails for promise that rejects 1`] = ` -"expect(received).resolves.toBe() +"expect(received).resolves.toBe() -Expected received Promise to resolve, instead it rejected to value - undefined" +Expected received Promise to resolve, instead it rejected to value + undefined" `; exports[`.resolves fails non-promise value "a" 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - string: \\"a\\"" + string: \\"a\\"" `; exports[`.resolves fails non-promise value [1] 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - array: [1]" + array: [1]" `; exports[`.resolves fails non-promise value [Function anonymous] 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - function: [Function anonymous]" + function: [Function anonymous]" `; exports[`.resolves fails non-promise value {"a": 1} 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - object: {\\"a\\": 1}" + object: {\\"a\\": 1}" `; exports[`.resolves fails non-promise value 4 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - number: 4" + number: 4" `; exports[`.resolves fails non-promise value null 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. -Received: null" +received value must be a Promise. +Received: null" `; exports[`.resolves fails non-promise value true 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. +received value must be a Promise. Received: - boolean: true" + boolean: true" `; exports[`.resolves fails non-promise value undefined 1`] = ` -"expect(received).resolves.toBeDefined() +"expect(received).resolves.toBeDefined() -received value must be a Promise. -Received: undefined" +received value must be a Promise. +Received: undefined" `; exports[`.toBe() does not crash on circular references 1`] = `