How to test graphql subscriptions
Tags: testing, graphql
Published: 2020-06-15 02:50:29

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:

``` ts
// 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](https://github.com/graphql/graphql-js/issues/735#issuecomment-302456482). 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:

``` ts
// 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:

``` ts
// 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:

``` ts
// 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:

```ts
// 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. 

```ts
// 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](https://gist.github.com/AugustoCalaca/60be39bc6825d01281a110e108063af0) and [UserAddedSubscription.ts](https://gist.github.com/AugustoCalaca/a6262676e37ca1964c1ecc3963587b17)