[ PART 5 ] Create a Twitter clone with GraphQL, Typescript, and React ( Adding tests for authorization )

[ PART 5 ] Create a Twitter clone with GraphQL, Typescript, and React ( Adding tests for authorization )

Hi everyone ;).

Github Repository

In the last article, we didn't write any tests to verify that our authChecker was doing its job. And this is the first "problem" I've encountered since I started this project. The test client provided by the apollo-server-testing library does not, from what I have seen, propose to directly add headers to our request. After a little research, I found a solution not very complicated in itself but I don't know if it could be better ;). I propose you my solution but don't hesitate to propose others. I may even have missed something in the documentation ;).

src/server.ts

export const defaultContext = ({ req, res }: any) => {
  return {
    req,
    res,
    db,
  }
}

export const schema = async () => {
  return await buildSchema({
    resolvers: [AuthResolver],
    authChecker: authChecker,
  })
}

const createServer = async () => {
  return new ApolloServer({
    schema: await schema(),
    context: defaultContext,
  })
}

export default createServer

I've separated things a bit in my createServer() method to be able to create the Test Server more easily.

src/tests/setup.ts

import { ApolloServer } from 'apollo-server'
import { createTestClient } from 'apollo-server-testing'
import { defaultContext, schema } from '../server'

export const testClient = async (ctxArg: any = { req: { headers: {} } }) => {
  return createTestClient(
    new ApolloServer({
      schema: await schema(),
      context: () => defaultContext(ctxArg),
    })
  )
}

By default, it would seem that the TestClient does not pass headers when making queries. Since our middleware verifies that we have a header authorization, we'll just have to pass our token when we want to test the authorizations with our JWT token.

In application, it gives this:

src/tests/auth.test.ts

test('it should fetch the currentUser', async () => {
  const user = await createUser()

  const { query } = await testClient({
    req: { headers: { authorization: 'Bearer ' + generateToken(user) } },
  })

  const res = await query({
    query: ME,
  })

  expect(res.data).not.toBeNull()
  expect(+res.data.me.id).toEqual(user.id)
})

test('it should throw an unauthorized error if there is no token', async () => {
  const user = await createUser()

  const { query } = await testClient()

  const res = await query({
    query: ME,
  })

  expect(res).toMatchSnapshot()
})

test('it should throw expired Token error', async () => {
  const user = await createUser()

  const { query } = await testClient({
    req: { headers: { authorization: 'Bearer ' + generateToken(user, -60) } },
  })

  const res = await query({
    query: ME,
  })

  expect(res).toMatchSnapshot()
})

src/tests/queries/auth.queries.ts

export const ME = gql`
  query {
    me {
      id
      username
      email
    }
  }
`

I also discovered the toMatchSnapshot() method from jest ;). Very handy, but I feel that it is to be used with a lot of care ;). For example, in the test performed for user registration, since we have created_at and updated_at fields that are different each time I run the test, my tests don't pass when I run the test again. So be sure that the answers will be the same at each test launch before using this method. But I'll have to read the jest doc in more detail ;).

Alt Text

That's it for today. Don't forget to have a look at the Github repository too from time to time if you are interested in my articles because I don't necessarily detail everything ;). For example, I modified the knexfile.ts file to add environment variables, etc...

Bye and take care ;)