Just write code isn't enough. We need to test it. The benefits this habit in programming are many ranging from ensure that the code work as designed to gain valuable time when refactoring your codebase.
Before testing we need to understand some things.
The subscribe execute has that signature besides having other fields that we won't need to this example:
// graphql-js
export function subscribe(
schema: GraphQLSchema,
document: DocumentNode,
rootValue?: any,
contextValue?: any,
variableValues?: Maybe<{ [key: string]: any }>,
): Promise<AsyncIterableIterator<ExecutionResult> | ExecutionResult>;
Fist our schema
with the required definitions, second our graphlq tag string which we will to use the parse
function to turn it into a DocumentNode
, followed by the rootValue
explained later, the contextValue
and lastly the variables required by our second argument.
The subscribe execute return a object with three promises: next()
, return()
and throw()
. Let's just stick to the next()
function which return our subscription fields once it is performed.
"But where and how do I trigger my subscription?" you may wonder.
Lets us understand the subscribe rootValue
argument. The root value represents the "top" of your metaphorical graph of data, and is useful to include functions or data to help resolve the root fields in your schema as Lee Byron explains very well in this comment. That's where we're gonna put our trigger to test our subscription.
The trigger is a PubSub
that must be somewhere of the our mutation. Something like:
// UserAddMutation.ts
...
await pubSub.publish(EVENTS.USER.ADDED, { userId: user._id });
...
As soon as this event is triggered all the subscriptions prepared to listen to it will receive the userId
value which is the shape of data that should be sent. See the code below:
// UserAddedSubscription.ts
import { subscriptionWithClientId } from 'graphql-relay-subscription';
import UserType from '../UserType';
import * as UserLoader from '../UserLoader';
import pubSub, { EVENTS } from '../../../channels/pubSub';
const UserAddedSubscription = subscriptionWithClientId({
...
subscribe: () => pubSub.asyncIterator(EVENTS.USER.ADDED),
getPayload: (root) => {
return { id: root.userId };
},
outputFields: {
user: {
type: UserType,
resolve: (root, _, context) => UserLoader.load(context, root.id),
},
},
});
export default UserAddedSubscription;
Great, our subscription is interested this event named EVENT.USER.ADDED
being able to listen to it and to receive the userId
value through the root
field on getPayload
function. Finally, the getPayload
sends this data to outputFields
and resolve the user type.
Notice that for our event to be published our mutation must be executed. In that case, we're gonna put all graphql mutation on rootValue
of the subscription like this:
// UserAddedSubscription.spec.ts
...
const contextValue = await getContext({});
const variablesMutation = {
input: {
name: 'Awesome Name',
username: 'awesomeusername',
email: 'awesome@email.com',
password: '12345',
},
};
const variablesSubscription = {
input: {},
};
// focus here
const triggerSubscription = graphql(schema, mutation, rootValue, contextValue, variablesMutation);
const result = await subscribe(schema, parse(subscription), triggerSubscription, contextValue, variablesSubscription);
...
Notice the parse
function and your role on grahql-js.
See also the variablesSubscription
and realize that it automatically adds a field named clientSubscriptionId
.
Below is the data that we want to receive when our subscription to execute:
// UserAddedSubscription.spec.ts
const subscription = `
subscription S($input: UserAddedInput!) {
UserAdded(input: $input) {
user {
name
username
email
isActive
followers {
totalCount
}
following {
totalCount
}
}
}
}
`;
In the stretch ahead we checked if the received values by the next()
function match the expected values. Realise that the name
and username
fields are the same as fields passed to variablesMutation
so our subscription is receiving the User
created at the time of the mutation in real-time.
// UserAddedSubscription.spec.ts
...
expect((await result.next()).value.data).toEqual({
UserAdded: {
user: {
name: 'Awesome Name',
username: 'awesomeusername',
email: null,
isActive: true,
followers: {
totalCount: 0,
},
following: {
totalCount: 0,
},
},
}
});
...
The complete code you can see in theses gists: UserAddedSubscription.spec.ts and UserAddedSubscription.ts
Top comments (3)
Nice tutorial!
I think it will be good to show other folks how to connect to socket.io by using graphql-transport-ws. Anyways thank you for sharing.
Thank you!
I thought about showing only the test to have a more objective post and right on target.
Which is cool bro! Also consider helping us to add socket.io.