Skip to content

Instantly share code, notes, and snippets.

@patrixr
Last active November 8, 2023 04:22
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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

saurav-bhagat commented May 5, 2021

e2e.buildGraphQLClient(serverInfo);

I cannot find this method in the helper file?

And how are our resolvers gonna bind here?

@patrixr
Copy link
Author

patrixr commented May 5, 2021

@saurav-bhagat that's just a new TestApolloClient(serverInfo)

@saurav-bhagat
Copy link

@saurav-bhagat that's just a new TestApolloClient(serverInfo)

Yeah, Thanks. It would be really helpful if you could post how we can create a testServer inside which the actual resolver and schemas go. I'm confused if actual resolvers are going to be called or we'll create mock resolvers?
If everything is separate then how our code is going to be tested?

@patrixr
Copy link
Author

patrixr commented May 6, 2021

@saurav-bhagat This is an end-to-end test. Meaning it runs your server on the machine, and uses a POST to call it.

Notice the // @TODO: Start up your test server here

You can just boot up your own server (most people use express+apollo server). This gist just shows how to E2E test against your own server

@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