Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions packages/core/test/common.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { EventEmitter, on } from 'events';
import { GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import isAsyncIterable from 'graphql/jsutils/isAsyncIterable';

const createPubSub = <TTopicPayload extends { [key: string]: unknown }>(emitter: EventEmitter) => {
return {
publish: <TTopic extends Extract<keyof TTopicPayload, string>>(topic: TTopic, payload: TTopicPayload[TTopic]) => {
emitter.emit(topic as string, payload);
},
subscribe: async function*<TTopic extends Extract<keyof TTopicPayload, string>>(
subscribe: async function* <TTopic extends Extract<keyof TTopicPayload, string>>(
topic: TTopic
): AsyncIterableIterator<TTopicPayload[TTopic]> {
const asyncIterator = on(emitter, topic);
Expand Down Expand Up @@ -52,7 +53,7 @@ const GraphQLSubscription = new GraphQLObjectType({
fields: {
ping: {
type: GraphQLString,
subscribe: async function*() {
subscribe: async function* () {
const stream = pubSub.subscribe('ping');
return yield* stream;
},
Expand All @@ -79,3 +80,17 @@ export const subscription = /* GraphQL */ `
ping
}
`;

export const collectAsyncIteratorValues = async <TType>(asyncIterable: AsyncIterableIterator<TType>): Promise<Array<TType>> => {
const values: Array<TType> = [];
for await (const value of asyncIterable) {
values.push(value);
}
return values;
};

export function assertAsyncIterator(input: unknown): asserts input is AsyncIterableIterator<unknown> {
if (!isAsyncIterable(input)) {
throw new Error('Expected AsyncIterable iterator.');
}
}
121 changes: 119 additions & 2 deletions packages/core/test/execute.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSpiedPlugin, createTestkit } from '@envelop/testing';
import { execute, GraphQLSchema } from 'graphql';
import { schema, query } from './common';
import { execute, ExecutionResult } from 'graphql';
import { ExecuteFunction } from 'packages/types/src';
import { schema, query, assertAsyncIterator, collectAsyncIteratorValues } from './common';

describe('execute', () => {
it('Should wrap and trigger events correctly', async () => {
Expand Down Expand Up @@ -133,4 +134,120 @@ describe('execute', () => {
setResult: expect.any(Function),
});
});

it('Should be able to manipulate streams', async () => {
const streamExecuteFn = async function* () {
for (const value of ['a', 'b', 'c', 'd']) {
yield { data: { alphabet: value } };
}
};

const teskit = createTestkit(
[
{
onExecute({ setExecuteFn }) {
setExecuteFn(streamExecuteFn);

return {
onExecuteDone: () => {
return {
onNext: ({ setResult }) => {
setResult({ data: { alphabet: 'x' } });
},
};
},
};
},
},
],
schema
);

const result: ReturnType<ExecuteFunction> = await teskit.executeRaw({} as any);
assertAsyncIterator(result);
const values = await collectAsyncIteratorValues(result);
expect(values).toEqual([
{ data: { alphabet: 'x' } },
{ data: { alphabet: 'x' } },
{ data: { alphabet: 'x' } },
{ data: { alphabet: 'x' } },
]);
});

it('Should be able to invoke something after the stream has ended.', async () => {
expect.assertions(1);
const streamExecuteFn = async function* () {
for (const value of ['a', 'b', 'c', 'd']) {
yield { data: { alphabet: value } };
}
};

const teskit = createTestkit(
[
{
onExecute({ setExecuteFn }) {
setExecuteFn(streamExecuteFn);

return {
onExecuteDone: () => {
let latestResult: ExecutionResult;
return {
onNext: ({ result }) => {
latestResult = result;
},
onEnd: () => {
expect(latestResult).toEqual({ data: { alphabet: 'd' } });
},
};
},
};
},
},
],
schema
);

const result: ReturnType<ExecuteFunction> = await teskit.executeRaw({} as any);
assertAsyncIterator(result);
// run AsyncGenerator
await collectAsyncIteratorValues(result);
});

it.skip('Should be able to invoke something after the stream has ended (manual return).', async () => {
expect.assertions(1);
const streamExecuteFn = async function* () {
for (const value of ['a', 'b', 'c', 'd']) {
yield { data: { alphabet: value } };
}
};

const teskit = createTestkit(
[
{
onExecute({ setExecuteFn }) {
setExecuteFn(streamExecuteFn);

return {
onExecuteDone: () => {
let latestResult: ExecutionResult;
return {
onNext: ({ result }) => {
latestResult = result;
},
onEnd: () => {
expect(latestResult).toEqual(undefined);
},
};
},
};
},
},
],
schema
);

const result: ReturnType<ExecuteFunction> = await teskit.executeRaw({} as any);
assertAsyncIterator(result);
result[Symbol.asyncIterator]().return!();
});
});
15 changes: 11 additions & 4 deletions packages/testing/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DocumentNode, ExecutionResult, GraphQLSchema, print } from 'graphql';
import { DocumentNode, ExecutionArgs, ExecutionResult, GraphQLSchema, print } from 'graphql';
import { getGraphQLParameters, processRequest, Push } from 'graphql-helix';
import { envelop, useSchema } from '@envelop/core';
import { Envelop, Plugin } from '@envelop/types';
import { envelop, makeExecute, useSchema } from '@envelop/core';
import { Envelop, ExecuteFunction, Plugin } from '@envelop/types';

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function createSpiedPlugin() {
Expand Down Expand Up @@ -57,6 +57,7 @@ export function createTestkit(
subscribe: (operation: DocumentNode | string, variables?: Record<string, any>, initialContext?: any) => Promise<Push<any, any>>;
replaceSchema: (schema: GraphQLSchema) => void;
wait: (ms: number) => Promise<void>;
executeRaw: ExecuteFunction;
} {
let replaceSchema: (s: GraphQLSchema) => void = () => {};

Expand Down Expand Up @@ -99,7 +100,6 @@ export function createTestkit(
contextFactory: initialContext ? () => proxy.contextFactory(initialContext) : proxy.contextFactory,
schema: proxy.schema,
});

return (r as any).payload as ExecutionResult;
},
subscribe: async (operation, rawVariables = {}, initialContext = null) => {
Expand Down Expand Up @@ -134,6 +134,13 @@ export function createTestkit(

return r;
},
executeRaw: makeExecute(async (args: ExecutionArgs) => {
const proxy = initRequest();
return await proxy.execute({
...args,
contextValue: await proxy.contextFactory(args.contextValue),
});
}),
Comment on lines +137 to +143
Copy link
Collaborator Author

@n1ru4l n1ru4l May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dotansimha At the moment the testkit is pretty helix tailored. I assume this is in order to be more integration test-like, and closer to how envelop is used with actual network layers?

I added this method here for now, but maybe we should in that case just construct all the stuff in place within the test?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove helix for testing, I initially used it because I wanted to test envelop with a request, but it's no longer needed.

};
}

Expand Down