End-to-end tests
To view this content, buy the book! 馃槂馃檹
Or if you鈥檝e already purchased.
End-to-end tests
If you鈥檙e jumping in here,
git checkout 24_0.2.0
(tag 24_0.2.0, or compare 24...25)
The final type of testing is end-to-end, or e2e. In backend e2e testing, we start the server and database, and then we test by sending HTTP requests to the server. So our tests will look something like this:
beforeAll(startE2EServer)
afterAll(stopE2EServer)
test('query A', () => {
const result = makeHttpRequest(queryA)
expect(result).toMatchSnapshot()
})
Let鈥檚 start by writing the startE2EServer()
helper. We want it to look like this:
export const startE2EServer = () => {
// start server and connect to db
return {
stop: () => // stops server and db client
request: () => // send http request to server
}
}
It returns the stop()
and request()
functions for the tests to use. We can fill in the first comment:
import { server } from '../src/'
import { connectToDB } from '../src/db'
export const startE2EServer = async () => {
// start server and connect to db
const e2eServer = await server.listen({ port: 0 })
await connectToDB()
return {
stop: () => // stops server and db client
request: () => // send http request to server
}
}
{ port: 0 }
uses any available port, which we do because the default port (4000) will be in use if our dev server is running while we run our tests. In order to await
our call to connectToDB()
, we need to make it async instead of callback-based:
export let db
export const connectToDB = async () => {
const client = new MongoClient(URL, { useNewUrlParser: true })
await client.connect()
db = client.db()
return client
}
We also need to return client
so that we can close the connection when testing is done. For stopping the server, there is a e2eServer.server.close
, but it鈥檚 callback-based. We can use node鈥檚 promisify()
to turn it into a Promise that we can await
:
import { promisify } from 'util'
export const startE2EServer = async () => {
const e2eServer = await server.listen({ port: 0 })
const dbClient = await connectToDB()
const stopServer = promisify(e2eServer.server.close.bind(e2eServer.server))
return {
stop: async () => {
await stopServer()
await dbClient.close()
}
request: () => // send http request to server
}
}
We also use bind to maintain the function鈥檚 this
.
We can make our function run faster by performing startup and stopping in parallel using Promise.all()
:
export const startE2EServer = async () => {
const [e2eServer, dbClient] = await Promise.all([
server.listen({ port: 0 }),
connectToDB()
])
const stopServer = promisify(e2eServer.server.close.bind(e2eServer.server))
return {
stop: () => Promise.all([stopServer(), dbClient.close()]),
request: () => // send http request to server
}
}
Lastly, we can send HTTP requests to the server using Apollo Link. apollo-link-http
has the basic HttpLink
and apollo-link
has execute()
, a function that sends GraphQL operations over a link, and toPromise()
, which converts the Observable that execute()
returns into a Promise. All together, that鈥檚:
import { promisify } from 'util'
import { HttpLink } from 'apollo-link-http'
import fetch from 'node-fetch'
import { execute, toPromise } from 'apollo-link'
import { server } from '../src/'
import { connectToDB } from '../src/db'
export const startE2EServer = async () => {
const [e2eServer, dbClient] = await Promise.all([
server.listen({ port: 0 }),
connectToDB()
])
const stopServer = promisify(e2eServer.server.close.bind(e2eServer.server))
const link = new HttpLink({
uri: e2eServer.url,
fetch
})
return {
stop: () => Promise.all([stopServer(), dbClient.close()]),
request: operation => toPromise(execute(link, operation))
}
}
We also need src/index.js
to add server
to its exports:
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources,
context,
formatError
})
...
export { server, typeDefs, resolvers, context, formatError }
Now we can write our e2e test:
import { gql, startE2EServer } from 'guide-test-utils'
let stop, request
beforeAll(async () => {
const server = await startE2EServer()
stop = server.stop
request = server.request
})
afterAll(() => stop())
const HELLO = gql`
query {
hello
}
`
test('hello', async () => {
const result = await request({ query: HELLO })
expect(result).toMatchSnapshot()
})
We start the server in beforeAll()
and stop it in afterAll()
. Then we create our query document, which we send to the server using request()
in our one test. After we run the test, we check the snapshot:
test/__snapshots__/e2e.test.js.snap
exports[`hello 1`] = `
Object {
"data": Object {
"hello": "馃實馃審馃寧",
},
}
`;
We can test with a set of operations that we write, or we could generate random operations with IBM鈥檚 query generator.