Skip to content

Commit a842678

Browse files
authored
polish: add tests for expectPromise and expectEqualPromisesOrValues (#3655)
* polish: add tests for more testUtils adds tests for expectPromise and expectEqualPromisesOrValues * break out expectMatchingValues into another file * apply review feedback
1 parent e8e174b commit a842678

7 files changed

+189
-45
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { expectEqualPromisesOrValues } from '../expectEqualPromisesOrValues';
5+
import { expectPromise } from '../expectPromise';
6+
7+
describe('expectEqualPromisesOrValues', () => {
8+
it('throws when given unequal values', () => {
9+
expect(() => expectEqualPromisesOrValues([{}, {}, { test: 'test' }])).throw(
10+
"expected { test: 'test' } to deeply equal {}",
11+
);
12+
});
13+
14+
it('does not throw when given equal values', () => {
15+
const testValue = { test: 'test' };
16+
expect(() =>
17+
expectEqualPromisesOrValues([testValue, testValue, testValue]),
18+
).not.to.throw();
19+
});
20+
21+
it('does not throw when given equal promises', async () => {
22+
const testValue = Promise.resolve({ test: 'test' });
23+
24+
await expectPromise(
25+
expectEqualPromisesOrValues([testValue, testValue, testValue]),
26+
).toResolve();
27+
});
28+
29+
it('throws when given unequal promises', async () => {
30+
await expectPromise(
31+
expectEqualPromisesOrValues([
32+
Promise.resolve({}),
33+
Promise.resolve({}),
34+
Promise.resolve({ test: 'test' }),
35+
]),
36+
).toRejectWith("expected { test: 'test' } to deeply equal {}");
37+
});
38+
39+
it('throws when given equal values that are mixtures of values and promises', () => {
40+
const testValue = { test: 'test' };
41+
expect(() =>
42+
expectEqualPromisesOrValues([testValue, Promise.resolve(testValue)]),
43+
).to.throw('Received an invalid mixture of promises and values.');
44+
});
45+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { expectEqualPromisesOrValues } from '../expectEqualPromisesOrValues';
5+
6+
describe('expectMatchingValues', () => {
7+
it('throws when given unequal values', () => {
8+
expect(() => expectEqualPromisesOrValues([{}, {}, { test: 'test' }])).throw(
9+
"expected { test: 'test' } to deeply equal {}",
10+
);
11+
});
12+
13+
it('does not throw when given equal values', () => {
14+
const testValue = { test: 'test' };
15+
expect(() =>
16+
expectEqualPromisesOrValues([testValue, testValue, testValue]),
17+
).not.to.throw();
18+
});
19+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { expectPromise } from '../expectPromise';
5+
6+
describe('expectPromise', () => {
7+
it('throws if passed a value', () => {
8+
expect(() => expectPromise({})).to.throw(
9+
"Expected a promise, received '{}'",
10+
);
11+
});
12+
13+
it('toResolve returns the resolved value', async () => {
14+
const testValue = {};
15+
const promise = Promise.resolve(testValue);
16+
expect(await expectPromise(promise).toResolve()).to.equal(testValue);
17+
});
18+
19+
it('toRejectWith throws if the promise does not reject', async () => {
20+
try {
21+
await expectPromise(Promise.resolve({})).toRejectWith(
22+
'foo',
23+
); /* c8 ignore start */
24+
} /* c8 ignore stop */ catch (err) {
25+
expect(err.message).to.equal(
26+
"Promise should have rejected with message 'foo', but resolved as '{}'",
27+
);
28+
}
29+
});
30+
31+
it('toRejectWith throws if the promise rejects with the wrong reason', async () => {
32+
try {
33+
await expectPromise(Promise.reject(new Error('foo'))).toRejectWith(
34+
'bar',
35+
); /* c8 ignore start */
36+
} /* c8 ignore stop */ catch (err) {
37+
expect(err.message).to.equal(
38+
"expected Error: foo to have property 'message' of 'bar', but got 'foo'",
39+
);
40+
}
41+
});
42+
43+
it('toRejectWith does not throw if the promise rejects with the right reason', async () => {
44+
try {
45+
await expectPromise(Promise.reject(new Error('foo'))).toRejectWith(
46+
'foo',
47+
); /* c8 ignore start */
48+
} catch (err) {
49+
// Not reached.
50+
expect.fail('promise threw unexpectedly');
51+
} /* c8 ignore stop */
52+
});
53+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { assert } from 'chai';
2+
3+
import { isPromise } from '../jsutils/isPromise';
4+
import type { PromiseOrValue } from '../jsutils/PromiseOrValue';
5+
6+
import { expectMatchingValues } from './expectMatchingValues';
7+
8+
export function expectEqualPromisesOrValues<T>(
9+
items: ReadonlyArray<PromiseOrValue<T>>,
10+
): PromiseOrValue<T> {
11+
const [firstItem, ...remainingItems] = items;
12+
if (isPromise(firstItem)) {
13+
if (remainingItems.every(isPromise)) {
14+
return Promise.all(items).then(expectMatchingValues);
15+
}
16+
} else if (remainingItems.every((item) => !isPromise(item))) {
17+
return expectMatchingValues(items);
18+
}
19+
20+
assert(false, 'Received an invalid mixture of promises and values.');
21+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { expectJSON } from './expectJSON';
2+
3+
export function expectMatchingValues<T>(values: ReadonlyArray<T>): T {
4+
const [firstValue, ...remainingValues] = values;
5+
for (const value of remainingValues) {
6+
expectJSON(value).toDeepEqual(firstValue);
7+
}
8+
return firstValue;
9+
}

src/__testUtils__/expectPromise.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { assert, expect } from 'chai';
2+
3+
import { inspect } from '../jsutils/inspect';
4+
import { isPromise } from '../jsutils/isPromise';
5+
6+
export function expectPromise(maybePromise: unknown) {
7+
assert(
8+
isPromise(maybePromise),
9+
`Expected a promise, received '${inspect(maybePromise)}'`,
10+
);
11+
12+
return {
13+
toResolve() {
14+
return maybePromise;
15+
},
16+
async toRejectWith(message: string) {
17+
let caughtError: Error | undefined;
18+
let resolved;
19+
let rejected = false;
20+
try {
21+
resolved = await maybePromise;
22+
} catch (error) {
23+
rejected = true;
24+
caughtError = error;
25+
}
26+
27+
assert(
28+
rejected,
29+
`Promise should have rejected with message '${message}', but resolved as '${inspect(
30+
resolved,
31+
)}'`,
32+
);
33+
34+
expect(caughtError).to.be.an.instanceOf(Error);
35+
expect(caughtError).to.have.property('message', message);
36+
},
37+
};
38+
}

src/execution/__tests__/subscribe-test.ts

Lines changed: 4 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { assert, expect } from 'chai';
22
import { describe, it } from 'mocha';
33

4+
import { expectEqualPromisesOrValues } from '../../__testUtils__/expectEqualPromisesOrValues';
45
import { expectJSON } from '../../__testUtils__/expectJSON';
6+
import { expectPromise } from '../../__testUtils__/expectPromise';
57
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick';
68

79
import { isAsyncIterable } from '../../jsutils/isAsyncIterable';
@@ -125,49 +127,6 @@ function createSubscription(pubsub: SimplePubSub<Email>) {
125127
return subscribe({ schema: emailSchema, document, rootValue: data });
126128
}
127129

128-
// TODO: consider adding this method to testUtils (with tests)
129-
function expectPromise(maybePromise: unknown) {
130-
assert(isPromise(maybePromise));
131-
132-
return {
133-
toResolve() {
134-
return maybePromise;
135-
},
136-
async toRejectWith(message: string) {
137-
let caughtError: Error;
138-
139-
try {
140-
/* c8 ignore next 2 */
141-
await maybePromise;
142-
expect.fail('promise should have thrown but did not');
143-
} catch (error) {
144-
caughtError = error;
145-
}
146-
147-
expect(caughtError).to.be.an.instanceOf(Error);
148-
expect(caughtError).to.have.property('message', message);
149-
},
150-
};
151-
}
152-
153-
// TODO: consider adding this method to testUtils (with tests)
154-
function expectEqualPromisesOrValues<T>(
155-
value1: PromiseOrValue<T>,
156-
value2: PromiseOrValue<T>,
157-
): PromiseOrValue<T> {
158-
if (isPromise(value1)) {
159-
assert(isPromise(value2));
160-
return Promise.all([value1, value2]).then((resolved) => {
161-
expectJSON(resolved[1]).toDeepEqual(resolved[0]);
162-
return resolved[0];
163-
});
164-
}
165-
166-
assert(!isPromise(value2));
167-
expectJSON(value2).toDeepEqual(value1);
168-
return value1;
169-
}
170-
171130
const DummyQueryType = new GraphQLObjectType({
172131
name: 'Query',
173132
fields: {
@@ -195,10 +154,10 @@ function subscribeWithBadFn(
195154
function subscribeWithBadArgs(
196155
args: ExecutionArgs,
197156
): PromiseOrValue<ExecutionResult | AsyncIterable<unknown>> {
198-
return expectEqualPromisesOrValues(
157+
return expectEqualPromisesOrValues([
199158
subscribe(args),
200159
createSourceEventStream(args),
201-
);
160+
]);
202161
}
203162

204163
/* eslint-disable @typescript-eslint/require-await */

0 commit comments

Comments
 (0)