Back to Blog

MERN Jest Testing(Part 5)

Aqida Haidari

Using Matchers

Jest enables you to test values in different ways by using matchers. We will introduce you to some of the matchers that are commonly used.

There are different matchers within jest like: string matchers, number matchers, array matchers, exceptions matchers, truthiness and more.

The simplest way to test a value is with exact equality.

  • toBe uses Object.is to test exact equality, You can use toBefor primitives like strings, numbers, or boolean.

describe("/users", () => {
    it("should respond with data when user is created", (done) => {
      server
        .post("/users")
        .send({
          username: "James",
          email: "james@gmail.com",
          name: "NEW James",
        })
        .set("Content-Type", "application/json")
        .set("Accept", "application/json")
        .end(async (err, result) => {
          if (err) {
            done(err);
          }
          expect(typeof result.body.username).toBe("string");

          done();
        });
    });
  });
  

In the code above example, expect will return an expectation object, all we need to do with this expectation object is to call toBe matcher on them. so we tested that type of username is equal to string exactly.

If you want to check the value of object, you should use toEqual or toStrictEqual.

  • toEqual recursively checks every field of an object or array.

it("should respond with data when user is created", (done) => {
      server
        .post("/users")
        .send({
          username: "James",
          email: "james@gmail.com",
          name: "NEW James",
        })
        .end(async (err, result) => {
          if (err) {
            done(err);
          }
          const status_code = { statusCode:200}
          expect({statusCode:result.statusCode}).toEqual(status_code);
          done();
        });
    });
    

In the above code snippet, we test equality for two objects. The received and expected value is the same { statusCode:200} , if you use toBe matcher instead of toEqual the test will fail.

  • toStrictEqual goes one step further than toEqual ******and also checks that two objects have the same type.
  • toBeDefined checks that a variable is not undefined. for example, we tested that an endpoint /users/ returns something.

describe("GET /users/", () => {
    it("should return all users", async () => {
      const response = await server.get("/users/");
      expect(response).toBeDefined()
    });
  });
  
  • toBeUndefined is the opposite of toBeDefined.
  • toBeTruthy is used when you want to ensure a value is true in a boolean context.

describe("GET /users/", () => {
  it("should return all users", async () => {
    const response = await server.get("/users/");
    expect(response).toBeTruthy()
  });
});

The above test will pass if there is value.

  • toBeFalsy is the opposite of BeTruthy .

describe("GET /users/", () => {
  it("should return all users", async () => {
    const response = await server.get("/users/");
    expect(response).not.toBeFalsy()
  });
});

We tested that the received value response is not false by using toBeFalsy and not before the matcher to test its opposite.

  • toBeNull is used when you want to check that something is null. and not.toBeNull is the opposite.

it("should respond with data when user is created", (done) => {
      server
        .post("/users")
        .send({
          username: "James",
          email: "james@gmail.com",
          name: "NEW James",
        })
        .end(async (err, result) => {
          if (err) {
            done(err);
          }
          expect(result.body.username).not.toBeNull();
          expect(result.body.email).not.toBeNull();
          expect(result.body.name).not.toBeNull();
          done();
        });
    });
    

In the above test, we used not.toBeNull() to check the username, email, and name properties are not null.

  • toHaveBeenCalled() is used to ensure that a mock function was called.

beforeAll(async () => {
    UserService.getUsers = jest.fn().mockResolvedValue([
      {
        username: 'mock',
        email: 'mock@user.com',
        name: 'Mockuser',
        createdAt: '2022-12-21T18:17:49.926Z',
      }
    ]);
  });
  

In the above code we have mocked and specified the return value for UserService.getUsers function.


it("should return list of mocked users", (done) => {
  server
    .get("/users")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .end(async (err, result) => {
      if (err) {
        done(err);
      }
      expect(UserService.getUsers).toHaveBeenCalled()
      done();
    });
});

Here we used toHaveBeenCalled() to ensure that UserService.getUsers mock function was called.

  • toHaveBeenCalledTimes(number) is used to ensure that a mock function got called exact number of times.

it("should return list of mocked users", (done) => {
  server
    .get("/users")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .end(async (err, result) => {
      if (err) {
        done(err);
      }
      expect(UserService.getUsers).toHaveBeenCalledTimes(1)
      done();
    });
});

In the above test UserService.getUsers mock function was called once.


it("should return list of mocked users", (done) => {
  server
    .get("/users")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .end(async (err, result) => {
      if (err) {
        done(err);
      }
      expect(UserService.getUsers).toHaveBeenCalledTimes(2)
      done();
    });
});

Here UserService.getUsers was called for the second time, then should set toHaveBeenCalledTimes()  with 2.

  • toHaveBeenCalledWith(arg1, arg2, …) is used to ensure that a mock function was called with specific arguments. Uses the same algorithm that toEqual uses for checking arguments.
  • test/users.test.js

beforeAll(async () => {
    UserService.getUser = jest.fn().mockResolvedValue({
	      _id:ObjectId('63a539c480bfd45a45a44cc3'),
	      username: "mock", 
	      email: "mock@user.com", 
	      name: "Mockuser"
    })
  });
  

Here we mocked getUser and specified its return value .


it("should return user",  (done) => {
      server.get(`/users/63a539c480bfd45a45a44cc3`)
      .then((response) => {
        expect(UserService.getUser).toHaveBeenCalledWith('63a539c480bfd45a45a44cc3')
        done();
      })
    });
    

We used toHaveBeenCalled to test mocked function UserService.getUser was called with63a539c480bfd45a45a44cc3 argument.

  • toHaveLength checks that an object has a length property and is set to a certain numeric value. And also useful for checking arrays or strings size.

it("should return list of mocked users", (done) => {
  server
    .get("/users")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .end(async (err, result) => {
      if (err) {
        done(err);
      }
      expect(result.body).toHaveLength(1)
      done();
    });
});

In above test used the mocked function with an array of objects as the returned value. To ensure that the array has size 1 , we set toHaveLength() matcher to 1.

  • toMatch checks that a string matches a regular expression or a string.

it("should respond with data when user is created", (done) => {
      server
        .post("/users")
        .send({
          username: "James",
          email: "james@gmail.com",
          name: "NEW James",
        })
        .end(async (err, result) => {
          if (err) {
            done(err);
          }
          expect(result.body.name).toMatch('NEW');
					expect(result.body.name).toMatch(/NEW/);
          done();
        });
    });
    

Above test checks that name property includes NEW string .

  • toMatchObject checks that an object matches a subset of the properties of an object.

it("should respond with data when user is created", (done) => {
      server
        .post("/users")
        .send({
          username: "James",
          email: "james@gmail.com",
          name: "NEW James",
        })
        .end(async (err, result) => {
          if (err) {
            done(err);
          }
          expect(result.body).toMatchObject({
            username: "James",
            email: "james@gmail.com",
            name: "NEW James",
          })
          done();
        });
    });
    

Here toMatchObject matches received object result.body with properties of expected objects.

  • toBeInstanceOf checks that an object is an instance of a class.

const { UserService } = require('../../service');
...

it('instance of', ()=>{
  expect(new UserService()).toBeInstanceOf(UserService);
})

Here we used to check  new UserService() is an instance of UserService class.

  • toHaveProperty checks that property at a provided reference exists for an object.

it("should respond with data when user is created", (done) => {
      server
        .post("/users")
        .send({
          username: "james",
          email: "james@gmail.com",
          name: "NEW James",
        })
        .end(async (err, result) => {
          if (err) {
            done(err);
          }
          expect(result.body).toHaveProperty('email')
          done();
        });
    });
    

The above example contains result.body object with some properties like username, email and name . we are using toHaveProperty to check for the existence of email property in the object.

  • toContain checks the existence of an item in an array. And uses a strict equality check === for testing.

it("should return list of mocked users", (done) => {
  server
    .get("/users")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .end(async (err, result) => {
      if (err) {
        done(err);
      }
      let names = [];
      for(var i=0; i; i++){
        names.push(result.body[i].name)
      }
      expect(names).toContain('tester')
      done();
    });
});

The above example contains an array of names, we used toContain matcher to check if tester name exists in the array or no.

  • arrayContaining matches a received array which contains all of the elements in the expected array. so expected array is a subset of received array.

it("should return list of mocked users", (done) => {
  server
    .get("/users")
    .set("Content-Type", "application/json")
    .set("Accept", "application/json")
    .end(async (err, result) => {
      if (err) {
        done(err);
      }
      let names = [];
      for(var i=0; i
  1. In the above test, the received value is an array of names  ['Mockuser','tester']
  2. The expected value is a subset of names ['tester']
  3. We used arrayContaining to check received value which is an array contains all array items in expected value.
  • toThrow(error) is used to test a function throws error when it is called.
  • controller/Users.controller.js

const AllUsers =  (req, res) => {
	...
  throw new Error('error')
};

const { AllUsers } = require('../../controller/Users.controllers')

...

  it("should throw",  () => {
    expect(AllUsers).toThrow('error')
});

Here we used toThrow to test if the AllUsers function throws error.

  • resolves extracts resolved value of a Promise so you can chain any other matcher on it.
  • controller/Users.controller.js

const AllUsers = (req, res) => {
  return new Promise((resolve, reject) => {
      resolve('yes')
  })
}

const { AllUsers } = require('../../controller/Users.controllers')

...

it('should resolves',() => {
  return expect(AllUsers()).resolves.toBe('yes')
})
  1. The received value is a promise that extracted its resolved value yes by resolves
  2. Then chained with toBe matcher to test the exact value.
  • rejects extracts rejected value of a Promise so you can chain any other matcher on it.
  • controller/Users.controller.js

const AllUsers = (req, res) => {
  return new Promise((resolve, reject) => {
      reject('no')
  })
}

const { AllUsers } = require('../../controller/Users.controllers')

..

it('should resolves',() => {
  return expect(AllUsers()).rejects.toBe('no')
})
  1. The received value is a promise that extracted its rejected value no by rejects .
  2. Then chained with toBe() matcher to test the exact value.

Share on social media: