Skip to content

Instantly share code, notes, and snippets.

@patrixr
Last active November 8, 2023 04:22
Show Gist options
  • Save patrixr/2536ee396d488bd5e38b0278513eefeb to your computer and use it in GitHub Desktop.
Save patrixr/2536ee396d488bd5e38b0278513eefeb to your computer and use it in GitHub Desktop.
//
// E2E Test Setup
// e2e_helpers.js
//
import { getMainDefinition } from '@apollo/client/utilities'
import { AuthPayload } from '../../lib/typings/goodchat'
import { WebSocketLink } from '@apollo/client/link/ws'
import { promisify } from 'util'
import fetch from 'cross-fetch'
import http from 'http'
import _ from "lodash"
import ws from 'ws'
import koa from 'koa'
import {
split,
HttpLink,
ApolloClient,
InMemoryCache,
DocumentNode,
SubscriptionOptions
} from '@apollo/client/core'
let server : http.Server
let url : string
let port : number
let host = '127.0.0.1'
export type TestServerInfo = {
port: number
host: string
}
export async function bootTestServer() : Promise<TestServerInfo> {
// @TODO: Start up your test server here
return {
host: '127.0.0.1',
port: 3000 // or whichever port you're using
}
}
export async function teardownTestServer() {
// @TODO: Shutdown your test server
}
/**
* Our test apollo client <3
*
* @export
* @class TestApolloClient
* @extends {ApolloClient<any>}
*/
export class TestApolloClient extends ApolloClient<any> {
private wsLink : WebSocketLink
constructor(serverInfo: TestServerInfo) {
const { url, host } = serverInfo;
const wsLink = new WebSocketLink({
// @TODO: Update WS url
uri: `ws://${host}:${port}/graphql/subscriptions`,
webSocketImpl: ws,
options: {
reconnect: false,
connectionParams: {
'Authorization': 'Bearer dummy'
}
}
});
const httpLink = new HttpLink({
// @TODO: update HTTP url
uri: `http://${host}:${port}/graphql`,
fetch
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
super({
link: splitLink,
cache: new InMemoryCache()
});
this.wsLink = wsLink
}
stop() {
super.stop();
//
// This is important, or else your test session will never close.
// Also the reason it inherits from ApolloClient instead of just creating it
// That way we can sneak in that WS closing
//
(this.wsLink as any).subscriptionClient.close();
}
}
//
//
// A very rough helper for testing subscription
//
// @TODO: look into finding better alternatives
//
//
type SubscriptionTestParams = {
client: TestApolloClient,
query: DocumentNode,
variables?: SubscriptionOptions
}
type WaitCondition = () => boolean
/**
* Helper to create a subscription with a bunch of handy methods
*
* @export
* @param {SubscriptionTestParams} params
* @returns
*/
export function createSubscription(params: SubscriptionTestParams) {
const results : any[] = [];
let error : any;
let observer = params.client.subscribe({
errorPolicy: 'all',
query: params.query,
variables: params.variables || {}
}).subscribe({
next(data) { results.push(data) },
error: (err) => { error = err }
})
return {
results,
observer,
disconnect() {
observer.unsubscribe();
},
wait(ms : number = 100) {
return new Promise(done => {
setTimeout(() => { done(null) }, ms);
});
},
waitForResults(opts : { len?: number, timeout?: number } = {}) {
return new Promise((done, fail) => {
let step = 2;
let sum = 0;
let timeout = opts.timeout ?? 30;
let len = opts.len ?? 1;
const interval = setInterval(() => {
if (!error && results.length >= len) {
clearInterval(interval);
return done(results);
}
if (error || sum >= timeout) {
error = error || new Error(
`Timeout: subscription did not receive the expected results after ${timeout}ms`
)
clearInterval(interval);
return fail(error);
}
sum += step
}, step);
})
},
get triggerCount() {
return results.length;
},
get error () {
return error;
}
}
}
//
// A TEST EXAMPLE !
//
import * as e2e from './e2e_helpers'
import { gql } from "apollo-server-core"
describe('Subscription Test', () => {
let serverInfo : e2e.TestServerInfo;
let gqlClient : e2e.TestApolloClient;
before(() => {
serverInfo = await e2e.bootTestServer();
})
after(async () => {
await e2e.teardownTestServer();
})
beforeEach(async () => {
gqlClient = new TestApolloClient(serverInfo);
});
afterEach(() => gqlClient.stop())
it('receives data !', async () => {
const sub = e2e.createSubscription({
client: gqlClient,
query: gql`
subscription newMessages {
messageEvent {
message {
id
content
conversationId
}
}
}
`
});
const message = await db.createMessage(); // Something that would trigger subscription event
await sub.waitForResults();
expect(sub.results).to.be.of.length(1); // Check for success
sub.disconnect(); // unsubscribe
})
})
@saurav-bhagat
Copy link

oh! Okay, Thanks for clearing out. Do you also have written unit tests before for subscription?

@bkrmalick
Copy link

bkrmalick commented Nov 8, 2023

for apollo v4 - it should now be

 const wsLink = new GraphQLWsLink(
        createClient({
          url: `ws://${host}:${port}/`,
          connectionParams: {
            Authorization: "Bearer xxx",
          },
          webSocketImpl: WebSocket,
        })
      );

thank you for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment