Coding Journal

Jordan Vidrine

Daily Coding Journal

An experienced business owner + musician with a demonstrated history of management in the Energy sector and an insatiable appetite for learning new and complex skills. Transitioning from an ownership role & seeking employment in Web Development as a Jr Full Stack Developer.

August 21st

Testing in Node with Jest

For the last part of the day yesterday I began going through the testing part of the node course I am taking. I love the idea of testing and am eager to continue to learn how to use Jest in reference to node, express, and a working database.

Yesterday we walked through setting up a test environment, a test database, and refactoring our call to express for us to be able to use it in a testing suite. We installed Jest, as well as Supertest (helps testing with express routes without creating a server) from the NPM library.

A jest test looks like this:


const calculateTip = (total, tipPercent = .25) => total + (total * tipPercent);

// Jest gives us the global variable of test, so no need to require anything
test('Should calculate total with tip', () => {
  const total = calculateTip(10,.3)
  expect(total).toBe(13)
})

This test would pass. Jest also works for Async programming as well. An async test will look similar to this.


const add = (a, b) => {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      if (a < 0 || b < 0) {
        return reject('Numbers must be non-negative')
      }
      resolve(a+b)
    },2000)
  })
}

test('Should add two numbers async/await', async () => {
  const sum = await add(10,22)
  expect(sum).toBe(32)
})

Jest lifecycle methods

Because each test in our task app will create documents in our mongo database, we need to be able to clear the db after each test runs. An issue we learned we would encounter if we didnt do this would be as follows:

I test that a user is created in my db.
This test can only run once correctly then would fail on subsequent attempts. Our code specifies no duplicate emails can exist in the user document, but the test is trying to create the same user on each pass.

To fix this we will use Jest Lifecycle Methods to run code before and after a test case.

We can add the following code to our test suite and define what we would like to do before and after each test we will be running.

beforeEach(()=>{
  // your code here
})

afterEach(()=>{
  // your code here
})

test('This will be a test', () => {
  // testing code here
})

Using supertest, which is SUPER COOL, because it uses the express routers to test the routes without having to create a server. This is interesting to me because it essentially seems to show me that Express is just a bunch of methods on a class that point to different things.

Anyway, heres the code we have created so far in testing our user routes and data:

const request = require('supertest')
const app = require('../src/app')
const User = require('../src/models/user')

// fake user data
const userOne = {
  name: 'User One',
  email: 'userOne@user.com',
  password: '1qaz7ujm'
}

// before each test, this function will clear the database as well as create a dummy user for us to use for other tests
beforeEach(async () => {
  await User.deleteMany()
  await new User(userOne).save()
})

test('Should signup a new user', async () => {
  await request(app).post('/users').send({
    name: 'Jordan',
    email: 'Jordan@jordanvidrine.com',
    password: '1qaz7ujm'
  }).expect(201)
})

test('Should login existing user', async () => {
  await request(app).post('/users/login').send({
    email: userOne.email,
    password: userOne.password
  }).expect(200)
})

test('Should not login nonexistent user', async () => {
  await request(app).post('/users/login').send({
    email: 'badUser@user.com',
    password: 'nonexistent'
  }).expect(400)
})

Testing with authentication

To be able to test authentication on users in our express app, we will need to include jwt and mongoose into our test suite. For our fake user we use to test, we create a token inside of the tokens array typically saved to a user object. To get this token, we will need an ID generated by mongoose. We do this like so:

const userOneId = new mongoose.Types.ObjectId()
const userOne = {
  _id: userOneId,
  name: 'User One',
  email: 'userOne@user.com',
  password: '1qaz7ujm',
  tokens: [{
    token: jwt.sign({ _id: userOneId }, process.env.JWT_SECRET)
  }]
}

Now we are all set to test our authentication in our app. Here is an example test for testing if a user is authenticated to read his/her profile as well as a test to show that a user cannot get their profile if they are not logged in (has a token in their token array).

test('Should get profile for user', async () => {
  await request(app)
      .get('/users/me')
      .set('Authorization', `Bearer ${userOne.tokens[0].token}`)
      .send()
      .expect(200)
})

test('Should not get profile for unauthenticated user', async () => {
  await request(app)
      .get('/users/me')
      .send()
      .expect(401)
})

Advanced assertions

Currently, we are only testing based on the status code that is sent back to the user. To get more in depth with our tests, it would be nice to get the response back, and run some tests on that. We can do that! The following code will save the response and then we can use that to get data from the database, making sure the user was created.

test('Should signup a new user', async () => {
  const response = await request(app).post('/users').send({
    name: 'Jordan',
    email: 'Jordan@jordanvidrine.com',
    password: '1qaz7ujm'
  }).expect(201)

  // Assert that the database was changed correctly
  const user = await User.findById(response.body.user._id)
  expect(user).not.toBeNull()

})

Jest also allows us to test if objects match by an object we pass into it. It checks to see if the keys and values we pass are in the object we are testing. If the object contains those items it is considered a match, even if other info is also there.

expect(response.body).toMatchObject({
  user: {
    name: 'Jordan',
    email: 'jordan@jordanvidrine.com'
  },
  token: user.tokens[0].token
})

This was my first real time using Jest or testing inside of node since taking this course. I have to say I really like the idea of all of this and how Jest seems to be working. Throughout this course I have fallen in love with back end web dev and its stuff like this that I find so interesting.

Back to blog...