Jest test fails?

Hello, i’m trying to add a test to getAllUsers function but I don’t know why the test is failing?
I think that the expectations are run before all the async code inside getAllUsers finishes!!!
do you have any suggestions?

this is the file being tested:

const { Op } = require('sequelize')
const { User } = require('../models')
const catchAsync = require('../utils/catchAsync')
const AppError = require('../utils/appError')

exports.getAllUsers = catchAsync(async (req, res, next) => {
  const users = await User.findAll({
    attributes: ['id', 'username', 'email', 'role', 'avatar'],
    where: {
      id: { [Op.gt]: 0 }
    }
  })
  if (!users.length) {
    return next(new AppError('no data found', 204))
  }
  res.status(200).json({
    status: 'success',
    data: users
  })
})

and this is the test code:


const userController = require('../controllers/userController')

describe('UserController', () => {
  const users = [
    {
      username: 'Admin',
      role: 'admin',
      avatar: 'bb',
      email: 'admin@gmail.com'
    },
    {
      username: 'User',
      role: 'user',
      avatar: 'bb',
      email: 'user@gmail.com'
    }
  ]
  test('Expect to respond with 200 code and users data', async () => {
    const req = {}
    const res = { status: jest.fn(() => res), json: jest.fn(() => res) }
    const next = jest.fn()
    await userController.getAllUsers(req, res, next) 
    expect(res.status).toHaveBeenCalledTimes(1)
    expect(res.status).toHaveBeenCalledWith(200)
    expect(res.json).toHaveBeenCalledTimes(1)
    expect(res.json).toHaveBeenCalledWith({
      status: 'success',
      data: users
    })
  })
})

thanks for the help :slightly_smiling_face:

It’s been a little while since I wrote a jest test. I have some observations.

await userController.getAllUsers(req, res, next) 

Rather than doing this here, I would expect this to be in a beforeAll block or something like that. But that may not matter.


const res = { status: jest.fn(() => res), json: jest.fn(() => res) }

I don’t understand this mock. First of all, in the callbacks, what is “res”? Is it a reference to the object you are creating? Is this an MC Escher painting? Or am I misunderstanding?

It’s further confusing because I see this in your code:

res.status(200).json({

So, what it is really expecting is that res is an object with a method called “status”. When that is called, it returns an object that has a method called “json”). That is not how you’ve mocked res in your test.

I would want my test mock to model what I have in the code. I would then test to see if res.status gets called with the value 200 and I’d want to see that in the object that that returns that that json method gets called with the appropriate value.


Yeah, I know this is frustrating. I would suggest throwing some console.log statements into your test and in your code to see what is happening. You can also put some in the callback functions in your mock functions.

If you have a repo, it might be easier to help you by being able to run it.

1 Like

hi @kevinSmith thanks for the help , basically this is userController for an express server app.

const res = { status: jest.fn(() => res), json: jest.fn(() => res) }

res contains status and json methods , this is the reason I’m mocking the return value of the two methods just to be able to call json() after calling status()
if i comment
const users = await User.findAll({ attributes: ['id', 'username', 'email', 'role', 'avatar'] })
and replaced with
const users =[1,2]
the test run normally and fails at the last asssertion as expected
so i conclude that jest runs the assertions before the function userController.getAllUsers(req, res, next) finishesit’s execution.
my question: is jest not able to wait for all the async code inside the getAllUsers to finish?

My issue wasn’t that you are mocking them both. My issue is that the structure of res in your mock and the structure of res in your code are different.

Your code looks like it is expecting res to be something this:

{
  status: () => ({
    json: () => {},
  }),
}

but your mock has this structure for res

{
  status: () => {},
  json: () => {},
}

json is not a method of res but is a method of the object returned by status. At least that is what I think your code is implying.

In fact, I’m surprised that this:

res.status(200).json({

doesn’t throw an error when you run the test.

Oh!!! Wait, I see. It’s the circular reference. Because status (at least in the test) returns res which does have a method json. OK, that is a really bizarre way to mock that. Your mock should match your data to confirm that the code is handling it properly.

I would expect something like this:

const mockStatusReturnObject = { json: jest.fn() }
const res = { status: jest.fn(() => mockStatusReturnObject) }

Then you can confirm that res.status is called and confirm that the object it returns has a method json that got called.

I haven’t written a jest test in 8 months, but that looks about right.

hello @ kevinSmith i don’t think that the mocking of res is causing this strange situation, because when i remove the async call to User.findAll all the tests pass!!!

OK, but tests passing and tests being good tests are two different things. I can write 1000 passing tests that don’t test anything. Your app is using one data structure and your test is using a completely different one. Sure, it kind of works out, but the purpose of writing tests is not to get them to pass, but to write tests that run your code as close to irl as possible to confirm that it doesn’t break.

And personally, I would have build this test differently:

describe - UserController
  set up my mocks, etc.
  beforeAll
    call getAllUsers
  test
    expect status to be called with 200
  test
    expect json to be called with the correct value  

I would do something like that.

I would also have to mocked User.findAll - I don’t want to test that at this point - that is a built in method and if we need to test the model we could do that separately. That is if this is a unit test and not a test testing the integration with the DB.

Actually, you are right @kevinSmith this is an integration test, but I oversimplified it to share it here, I couldn’t understand why the async call cause this!!!
thanks for the help :slightly_smiling_face:

I’m not sure why the async breaks the test. I would expect it to be required. It’s been a while for me. I would look at jest’s excellent documentation, especially the section on async tests.

I still don’t particularly like the idea of running this test against the DB - I’d rather make sure everything is tested in a unit test before I worried about integration. And then with integration I’d focus mainly on the end result, not expecting every little action in the middle.

Also, if your running against a live DB, I would expect some setup and teardown code, usually in a root level, a beforeAll and an afterAll.

In any case, I’d want to look and see what

  const foo = User.findAll({
    attributes: ['id', 'username', 'email', 'role', 'avatar'],
    where: {
      id: { [Op.gt]: 0 }
    }
  })

in your code and log out the result. I would expect that to be a promise. Run it in the live code and in the test to see what you get.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.