miragejs / graphql

A library for handling GraphQL requests with Mirage JS
MIT License
74 stars 12 forks source link

Mocking Subscriptions/ Subscriptions in Resolver #35

Open chrismllr opened 3 years ago

chrismllr commented 3 years ago

Any suggestions on how to handle mocking Subscriptions? I'm thinking through a way to do it using the graphql-subscriptions in tandem with mock-socket libraries, but it might be a little heavy.

jneurock commented 3 years ago

Sorry, I don't personally have any suggestions. It would be really nice to have an official way to do this. AFAIK, Mirage doesn't have a built-in solution for dealing with web sockets but discussions have been had on a proposed API https://github.com/miragejs/discuss/issues/23.

Feel free to share a use case here and any ideas you have for a good API and I'd be happy to put some work into it. I've been hesitant to add anything to this library so it may be good to use this project as a proving ground for a solution and then see if we can get something official into Mirage itself.

chrismllr commented 3 years ago

@jneurock Thanks for the response!

I attempted to go so far as to using graphql-subscriptions and subscriptions-transport-ws libraries in my mirage server, but that felt pretty heavy handed.

For the time being, I opted to create a test helper method to add the mocked server to the mirage server context, being sure to use the same wsURL im using to set up the Apollo WebsocketLink:

// Note-- this is the only lib that worked, mock-socket did not work properly
import { Server } from 'mock-socket-with-protocol';

import type { TestContext } from 'ember-test-helpers';
import Config from 'app/config/environment';
import { assert } from '@ember/debug';

let operationMap: Record<string, string> = {};

export function buildSubscriptionMessage(
  operationName: string,
  payload: Record<string, any>
): string {
  return JSON.stringify({
    type: 'data',
    payload,
    id: operationMap[operationName],
  });
}

export function setupWebsocketServer(hooks: NestedHooks): void {
  hooks.beforeEach(function (this: TestContext) {
    assert(
      '[setupWebsocketServer] You must place `setupWebsocketServer` after `setupMirage`.',
      this.server
    );

    this.server.ws = new Server(Config.apollo.wsURL);

    this.server.ws.on('connection', (socket: WebSocket) => {
      socket.on('message', (data: string) => {
        const msg = JSON.parse(data);
        if (!msg.payload?.operationName) {
          return;
        }
        operationMap[msg.payload.operationName] = msg.id;
      });
    });
  });

  hooks.afterEach(function (this: TestContext) {
    operationMap = {};
    this.server.ws.stop();
  });
}

The utility function buildSubscriptionMessage is used to mock-send messages "from the backend":


// send example message
describe('a test suite!', function(hooks) {
  setupMirage(hooks)
  setupWebsocketServer(hooks)

  test('a test', function(this: TestContext, assert) {
    const done = assert.async();

    this.server.ws.send(
      buildSubscriptionMessage(
        'SubOperationName', 
        {
          id: '123',
          moreData: 'Data'
        }
      )
    );

    // use a mocked handler somewhere to call done();
    assert.verifySteps();
  });
});

This is obviously Very pared down; subscriptions-ws-transport has a very thorough API for building the messages that are delivered to the UI -- this naive buildSubscriptionMessage will surely need to be fleshed out more to get more types of messages covered but for now, it seems to be getting the job done simply without re-building too much backend logic 🤷

Edit: Updated the solution based on how it has evolved for us!

jneurock commented 2 years ago

Super delayed response from my side...

Very cool. It seems this is largely an issue with Mirage itself lacking WebSocket support. There's some interest in refactoring Mirage and decoupling it from Pretender to enable usage of other server mocking libraries: https://github.com/miragejs/miragejs/issues/1013. It would be awesome to just support mocking subscriptions out-of-the-box.