User integration tests
Implementing integration tests for the User data type
To view this content, buy the book! 😃🙏
Or if you’ve already purchased.
User integration tests
If you’re jumping in here,
git checkout 22_0.2.0
(tag 22_0.2.0, or compare 22...23)
Let’s try to meet our 40% coverage threshold. Looking at src/resolvers/User.js
, we can see that our queries are red:
This makes sense, as our tests haven’t sent any user queries—they’ve just selected User
fields in review operations. Accordingly, when we scroll down, we see the only covered lines are for User
field resolvers:
Let’s write some integration tests that query user operations. We’ll start with the same imports and test format (one for each operation) as we did with Review.test.js
:
import {
createTestServer,
createTestClient,
gql,
mockUser
} from 'guide-test-utils'
test('me', async () => {
...
})
test('user', async () => {
...
})
test('searchUsers', async () => {
...
})
test('createUser', async () => {
...
})
For the me
test, we can set the context
to a user with a certain _id
, and then check to make sure the result’s id
matches:
const ME = gql`
query {
me {
id
}
}
`
test('me', async () => {
const { server } = createTestServer({
context: () => ({ user: { _id: 'itme' } })
})
const { query } = createTestClient(server)
const result = await query({ query: ME })
expect(result.data.me.id).toEqual('itme')
})
We don’t need to worry about selecting and testing other fields, as we know they’ve been covered.
Next is the user
query. We know our mock users collection always returns mockUser
, so we’ll query for that user:
const USER = gql`
query User($id: ID!) {
user(id: $id) {
id
}
}
`
test('user', async () => {
const { server } = createTestServer()
const { query } = createTestClient(server)
const id = mockUser._id.toString()
const result = await query({
query: USER,
variables: { id }
})
expect(result.data.user.id).toEqual(id)
})
For the searchUsers
test, let’s set it up so that multiple results are returned. For that, we’ll need to temporarily change the mocked users.find
function. To get access to that function, we need to get the dataSources from createTestServer()
:
test('searchUsers', async () => {
const userA = { _id: 'A' }
const userB = { _id: 'B' }
const { server, dataSources } = createTestServer()
dataSources.users.collection.find.mockReturnValueOnce({
toArray: jest.fn().mockResolvedValue([userA, userB])
})
mockReturnValueOnce()
will have users.find
return the given value once and then go back to returning [mockUser]
as it was before. After we make the query, we can also test to see what users.find
was called with:
expect(dataSources.users.collection.find).toHaveBeenCalledWith({
$text: { $search: 'foo' }
})
All together, that’s:
const SEARCH_USERS = gql`
query SearchUsers($term: String!) {
searchUsers(term: $term) {
... on User {
id
}
}
}
`
test('searchUsers', async () => {
const userA = { _id: 'A' }
const userB = { _id: 'B' }
const { server, dataSources } = createTestServer()
dataSources.users.collection.find.mockReturnValueOnce({
toArray: jest.fn().mockResolvedValue([userA, userB])
})
const { query } = createTestClient(server)
const result = await query({
query: SEARCH_USERS,
variables: { term: 'foo' }
})
expect(dataSources.users.collection.find).toHaveBeenCalledWith({
$text: { $search: 'foo' }
})
expect(result.data.searchUsers[0].id).toEqual('A')
expect(result.data.searchUsers[1].id).toEqual('B')
})
For the last test, our createUser
mutation will be calling users.insertOne
, which we haven’t mocked yet. Let’s reuse the insertOne
function we used for reviews:
const insertOne = jest.fn(
doc => (doc._id = new ObjectId('5cf8b6ff37568a1fa500ba4e'))
)
export const createTestServer = ({ context = defaultContext } = {}) => {
const reviews = new Reviews({
find: jest.fn(() => ({
toArray: jest.fn().mockResolvedValue(mockReviews)
})),
insertOne
})
const users = new Users({
createIndex: jest.fn(),
find: jest.fn(() => ({
toArray: jest.fn().mockResolvedValue(mockUsers)
})),
insertOne
})
...
For the mutation input, let’s pick
the fields from mockUser
:
import { pick } from 'lodash'
const CREATE_USER = gql`
mutation CreateUser($user: CreateUserInput!, $secretKey: String!) {
createUser(user: $user, secretKey: $secretKey) {
id
}
}
`
test('createUser', async () => {
const { server } = createTestServer()
const { mutate } = createTestClient(server)
const user = pick(mockUser, [
'firstName',
'lastName',
'username',
'email',
'authId'
])
const result = await mutate({
mutation: CREATE_USER,
variables: {
user,
secretKey: process.env.SECRET_KEY
}
})
expect(result).toMatchSnapshot()
})
Whenever we’re using a snapshot, we should check it on the first run to make sure it’s correct. If we run npm test
, then we should see a new file:
src/resolvers/__snapshots__/User.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`createUser 1`] = `
Object {
"data": Object {
"createUser": Object {
"id": "5cf8b6ff37568a1fa500ba4e",
},
},
"errors": undefined,
"extensions": undefined,
"http": Object {
"headers": Headers {
Symbol(map): Object {},
},
},
}
`;
Looks good! ✅ We can also see that our statement coverage is above the 40% minimum, so our tests pass!