From 51ccf2e1f671535c62eae2f34f5a2dcc20d937ad Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 21 Jun 2022 13:08:19 +0300 Subject: [PATCH 1/3] polish: add tests for more testUtils adds tests for expectPromise and expectEqualPromisesOrValues --- .../expectEqualPromisesOrValues-test.ts | 45 ++++++++++++++++ .../__tests__/expectPromise-test.ts | 53 +++++++++++++++++++ .../expectEqualPromisesOrValues.ts | 31 +++++++++++ src/__testUtils__/expectPromise.ts | 38 +++++++++++++ src/execution/__tests__/subscribe-test.ts | 49 ++--------------- 5 files changed, 171 insertions(+), 45 deletions(-) create mode 100644 src/__testUtils__/__tests__/expectEqualPromisesOrValues-test.ts create mode 100644 src/__testUtils__/__tests__/expectPromise-test.ts create mode 100644 src/__testUtils__/expectEqualPromisesOrValues.ts create mode 100644 src/__testUtils__/expectPromise.ts diff --git a/src/__testUtils__/__tests__/expectEqualPromisesOrValues-test.ts b/src/__testUtils__/__tests__/expectEqualPromisesOrValues-test.ts new file mode 100644 index 0000000000..71dcc0b337 --- /dev/null +++ b/src/__testUtils__/__tests__/expectEqualPromisesOrValues-test.ts @@ -0,0 +1,45 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { expectEqualPromisesOrValues } from '../expectEqualPromisesOrValues'; +import { expectPromise } from '../expectPromise'; + +describe('expectEqualPromisesOrValues', () => { + it('throws when given unequal values', () => { + expect(() => expectEqualPromisesOrValues([{}, {}, { test: 'test' }])).throw( + "expected { test: 'test' } to deeply equal {}", + ); + }); + + it('does not throw when given equal values', () => { + const testValue = { test: 'test' }; + expect(() => + expectEqualPromisesOrValues([testValue, testValue, testValue]), + ).not.to.throw(); + }); + + it('does not throw when given equal promises', async () => { + const testValue = Promise.resolve({ test: 'test' }); + + await expectPromise( + expectEqualPromisesOrValues([testValue, testValue, testValue]), + ).toResolve(); + }); + + it('throws when given unequal promises', async () => { + await expectPromise( + expectEqualPromisesOrValues([ + Promise.resolve({}), + Promise.resolve({}), + Promise.resolve({ test: 'test' }), + ]), + ).toRejectWith("expected { test: 'test' } to deeply equal {}"); + }); + + it('throws when given equal values that are mixtures of values and promises', () => { + const testValue = { test: 'test' }; + expect(() => + expectEqualPromisesOrValues([testValue, Promise.resolve(testValue)]), + ).to.throw('Received an invalid mixture of promises and values.'); + }); +}); diff --git a/src/__testUtils__/__tests__/expectPromise-test.ts b/src/__testUtils__/__tests__/expectPromise-test.ts new file mode 100644 index 0000000000..240c027b9c --- /dev/null +++ b/src/__testUtils__/__tests__/expectPromise-test.ts @@ -0,0 +1,53 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { expectPromise } from '../expectPromise'; + +describe('expectPromise', () => { + it('throws if passed a value', () => { + expect(() => expectPromise({})).to.throw( + "Expected a promise, received '{}'", + ); + }); + + it('toResolve returns the resolved value', async () => { + const testValue = {}; + const promise = Promise.resolve(testValue); + expect(await expectPromise(promise).toResolve()).to.equal(testValue); + }); + + it('toRejectWith throws if the promise does not reject', async () => { + try { + await expectPromise(Promise.resolve({})).toRejectWith( + 'foo', + ); /* c8 ignore start */ + } /* c8 ignore stop */ catch (err) { + expect(err.message).to.equal( + "Promise should have rejected with message 'foo', but resolved as '{}'", + ); + } + }); + + it('toRejectWith throws if the promise rejects with the wrong reason', async () => { + try { + await expectPromise(Promise.reject(new Error('foo'))).toRejectWith( + 'bar', + ); /* c8 ignore start */ + } /* c8 ignore stop */ catch (err) { + expect(err.message).to.equal( + "expected Error: foo to have property 'message' of 'bar', but got 'foo'", + ); + } + }); + + it('toRejectWith does not throw if the promise rejects with the right reason', async () => { + try { + await expectPromise(Promise.reject(new Error('foo'))).toRejectWith( + 'foo', + ); /* c8 ignore start */ + } catch (err) { + // Not reached. + expect.fail('promise threw unexpectedly'); + } /* c8 ignore stop */ + }); +}); diff --git a/src/__testUtils__/expectEqualPromisesOrValues.ts b/src/__testUtils__/expectEqualPromisesOrValues.ts new file mode 100644 index 0000000000..43f0ff9794 --- /dev/null +++ b/src/__testUtils__/expectEqualPromisesOrValues.ts @@ -0,0 +1,31 @@ +import { assert } from 'chai'; + +import { isPromise } from '../jsutils/isPromise'; +import type { PromiseOrValue } from '../jsutils/PromiseOrValue'; + +import { expectJSON } from './expectJSON'; + +export function expectEqualPromisesOrValues( + items: ReadonlyArray>, +): PromiseOrValue { + const remainingItems = items.slice(); + const firstItem = remainingItems.shift(); + + if (isPromise(firstItem)) { + if (remainingItems.every(isPromise)) { + return Promise.all(items).then(expectMatchingValues); + } + } else if (remainingItems.every((item) => !isPromise(item))) { + return expectMatchingValues(items); + } + + assert(false, 'Received an invalid mixture of promises and values.'); +} + +function expectMatchingValues(values: ReadonlyArray): T { + const remainingValues = values.slice(1); + for (const value of remainingValues) { + expectJSON(value).toDeepEqual(values[0]); + } + return values[0]; +} diff --git a/src/__testUtils__/expectPromise.ts b/src/__testUtils__/expectPromise.ts new file mode 100644 index 0000000000..59315ed7d0 --- /dev/null +++ b/src/__testUtils__/expectPromise.ts @@ -0,0 +1,38 @@ +import { assert, expect } from 'chai'; + +import { inspect } from '../jsutils/inspect'; +import { isPromise } from '../jsutils/isPromise'; + +export function expectPromise(maybePromise: unknown) { + assert( + isPromise(maybePromise), + `Expected a promise, received '${inspect(maybePromise)}'`, + ); + + return { + toResolve() { + return maybePromise; + }, + async toRejectWith(message: string) { + let caughtError: Error | undefined; + let resolved; + let rejected = false; + try { + resolved = await maybePromise; + } catch (error) { + rejected = true; + caughtError = error; + } + + assert( + rejected, + `Promise should have rejected with message '${message}', but resolved as '${inspect( + resolved, + )}'`, + ); + + expect(caughtError).to.be.an.instanceOf(Error); + expect(caughtError).to.have.property('message', message); + }, + }; +} diff --git a/src/execution/__tests__/subscribe-test.ts b/src/execution/__tests__/subscribe-test.ts index 5f256ca868..793a53f59f 100644 --- a/src/execution/__tests__/subscribe-test.ts +++ b/src/execution/__tests__/subscribe-test.ts @@ -1,7 +1,9 @@ import { assert, expect } from 'chai'; import { describe, it } from 'mocha'; +import { expectEqualPromisesOrValues } from '../../__testUtils__/expectEqualPromisesOrValues'; import { expectJSON } from '../../__testUtils__/expectJSON'; +import { expectPromise } from '../../__testUtils__/expectPromise'; import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick'; import { isAsyncIterable } from '../../jsutils/isAsyncIterable'; @@ -125,49 +127,6 @@ function createSubscription(pubsub: SimplePubSub) { return subscribe({ schema: emailSchema, document, rootValue: data }); } -// TODO: consider adding this method to testUtils (with tests) -function expectPromise(maybePromise: unknown) { - assert(isPromise(maybePromise)); - - return { - toResolve() { - return maybePromise; - }, - async toRejectWith(message: string) { - let caughtError: Error; - - try { - /* c8 ignore next 2 */ - await maybePromise; - expect.fail('promise should have thrown but did not'); - } catch (error) { - caughtError = error; - } - - expect(caughtError).to.be.an.instanceOf(Error); - expect(caughtError).to.have.property('message', message); - }, - }; -} - -// TODO: consider adding this method to testUtils (with tests) -function expectEqualPromisesOrValues( - value1: PromiseOrValue, - value2: PromiseOrValue, -): PromiseOrValue { - if (isPromise(value1)) { - assert(isPromise(value2)); - return Promise.all([value1, value2]).then((resolved) => { - expectJSON(resolved[1]).toDeepEqual(resolved[0]); - return resolved[0]; - }); - } - - assert(!isPromise(value2)); - expectJSON(value2).toDeepEqual(value1); - return value1; -} - const DummyQueryType = new GraphQLObjectType({ name: 'Query', fields: { @@ -195,10 +154,10 @@ function subscribeWithBadFn( function subscribeWithBadArgs( args: ExecutionArgs, ): PromiseOrValue> { - return expectEqualPromisesOrValues( + return expectEqualPromisesOrValues([ subscribe(args), createSourceEventStream(args), - ); + ]); } /* eslint-disable @typescript-eslint/require-await */ From cfefdc3563900f4250288045f0bebdb2b9589639 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Tue, 21 Jun 2022 13:34:19 +0300 Subject: [PATCH 2/3] break out expectMatchingValues into another file --- .../__tests__/expectMatchingValues-test.ts | 19 +++++++++++++++++++ .../expectEqualPromisesOrValues.ts | 10 +--------- src/__testUtils__/expectMatchingValues.ts | 9 +++++++++ 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 src/__testUtils__/__tests__/expectMatchingValues-test.ts create mode 100644 src/__testUtils__/expectMatchingValues.ts diff --git a/src/__testUtils__/__tests__/expectMatchingValues-test.ts b/src/__testUtils__/__tests__/expectMatchingValues-test.ts new file mode 100644 index 0000000000..8db828b723 --- /dev/null +++ b/src/__testUtils__/__tests__/expectMatchingValues-test.ts @@ -0,0 +1,19 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { expectEqualPromisesOrValues } from '../expectEqualPromisesOrValues'; + +describe('expectMatchingValues', () => { + it('throws when given unequal values', () => { + expect(() => expectEqualPromisesOrValues([{}, {}, { test: 'test' }])).throw( + "expected { test: 'test' } to deeply equal {}", + ); + }); + + it('does not throw when given equal values', () => { + const testValue = { test: 'test' }; + expect(() => + expectEqualPromisesOrValues([testValue, testValue, testValue]), + ).not.to.throw(); + }); +}); diff --git a/src/__testUtils__/expectEqualPromisesOrValues.ts b/src/__testUtils__/expectEqualPromisesOrValues.ts index 43f0ff9794..dc93a8ff0c 100644 --- a/src/__testUtils__/expectEqualPromisesOrValues.ts +++ b/src/__testUtils__/expectEqualPromisesOrValues.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; import { isPromise } from '../jsutils/isPromise'; import type { PromiseOrValue } from '../jsutils/PromiseOrValue'; -import { expectJSON } from './expectJSON'; +import { expectMatchingValues } from './expectMatchingValues'; export function expectEqualPromisesOrValues( items: ReadonlyArray>, @@ -21,11 +21,3 @@ export function expectEqualPromisesOrValues( assert(false, 'Received an invalid mixture of promises and values.'); } - -function expectMatchingValues(values: ReadonlyArray): T { - const remainingValues = values.slice(1); - for (const value of remainingValues) { - expectJSON(value).toDeepEqual(values[0]); - } - return values[0]; -} diff --git a/src/__testUtils__/expectMatchingValues.ts b/src/__testUtils__/expectMatchingValues.ts new file mode 100644 index 0000000000..695e4868fc --- /dev/null +++ b/src/__testUtils__/expectMatchingValues.ts @@ -0,0 +1,9 @@ +import { expectJSON } from './expectJSON'; + +export function expectMatchingValues(values: ReadonlyArray): T { + const remainingValues = values.slice(1); + for (const value of remainingValues) { + expectJSON(value).toDeepEqual(values[0]); + } + return values[0]; +} From 2f7d9548bf8873b864b11e25659c0ef1b46d0259 Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 14 Aug 2022 23:16:15 +0300 Subject: [PATCH 3/3] apply review feedback --- src/__testUtils__/expectEqualPromisesOrValues.ts | 4 +--- src/__testUtils__/expectMatchingValues.ts | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/__testUtils__/expectEqualPromisesOrValues.ts b/src/__testUtils__/expectEqualPromisesOrValues.ts index dc93a8ff0c..d7e4adcece 100644 --- a/src/__testUtils__/expectEqualPromisesOrValues.ts +++ b/src/__testUtils__/expectEqualPromisesOrValues.ts @@ -8,9 +8,7 @@ import { expectMatchingValues } from './expectMatchingValues'; export function expectEqualPromisesOrValues( items: ReadonlyArray>, ): PromiseOrValue { - const remainingItems = items.slice(); - const firstItem = remainingItems.shift(); - + const [firstItem, ...remainingItems] = items; if (isPromise(firstItem)) { if (remainingItems.every(isPromise)) { return Promise.all(items).then(expectMatchingValues); diff --git a/src/__testUtils__/expectMatchingValues.ts b/src/__testUtils__/expectMatchingValues.ts index 695e4868fc..87f8fa7f62 100644 --- a/src/__testUtils__/expectMatchingValues.ts +++ b/src/__testUtils__/expectMatchingValues.ts @@ -1,9 +1,9 @@ import { expectJSON } from './expectJSON'; export function expectMatchingValues(values: ReadonlyArray): T { - const remainingValues = values.slice(1); + const [firstValue, ...remainingValues] = values; for (const value of remainingValues) { - expectJSON(value).toDeepEqual(values[0]); + expectJSON(value).toDeepEqual(firstValue); } - return values[0]; + return firstValue; }