Home > Blockchain >  Mongoose model promise returns a response instead of undefined upon rejection
Mongoose model promise returns a response instead of undefined upon rejection

Time:12-03

This is my controller function I want to test

const register = async (req, res) => {
    const user = await User.create({
        name: req.body.name,
        username: req.body.username,
        password: req.body.password,
        email: req.body.email
    }).catch(error => handleServerErrorResponse(res));
    console.log('isUserRes?', user === res);
    if(user) res.status(201).json(user.toJSON());
}

This is my handleServerErrorResponse function

handleServerErrorResponse: (res, error = 'A server error occured') => {
    return res.status(500).send({
        message: error
    });
},

This is my test implementation (I'm trying to mock mongoose model 'create' call and have it fail)

it('should fail', async () => {

    // arrange
    jest.spyOn(User, 'create').mockRejectedValue('error');
    const req = mockRequest();
    const res = mockResponse();
    req.body = payload;

    // act
    await register(req, res);

    // assert
    expect(res.status).toHaveBeenCalledWith(500);
    expect(res.send).toHaveBeenCalledWith(
        expect.objectContaining({
            message: 'A server error occured'
        })
    );
})

Running the tests gives me the following

jest --silent=false --testEnvironment=node --runInBand --detectOpenHandles --coverage ./tests

console.log
    isUserRes? true

● register › should fail

    TypeError: user.toJSON is not a function

My question is why is const user not undefined when the create Promise fails? I've created a jsbin here that shows the value remains undefined when the await Promise fails. So what am I doing wrong here? And why is it referencing the response object?

Note: Even when sending real data using a HTTP client, if the create promise fails, the const user still references the response object and becomes truthy, failing my check. I am aware that this can be fixed using a then handler

.then(user => res.status(201).json(user));

but I am curious to know why is it behaving like this.

Update: Changed my handleServerErrorResponse function to this

handleServerErrorResponse: (res, error = 'A server error occured') => {
    res.status(500).send({
        message: error
    });
    return;
},

As suggested by haggbart, this function was responsible for setting the user to the returned response object.

CodePudding user response:

I think the problem here is that you're catching the error in the register function and returning the result of calling handleServerErrorResponse, rather than just rethrowing the error. This means that the user variable will be assigned the result of calling handleServerErrorResponse, which is the response object.

Instead of catching the error in the register function, you could rethrow it and catch it in the test itself, like this:

const register = async (req, res) => {
    const user = await User.create({
        name: req.body.name,
        username: req.body.username,
        password: req.body.password,
        email: req.body.email
    });

    console.log('isUserRes?', user === res);
    if(user) res.status(201).json(user.toJSON());
}

it('should fail', async () => {

    // arrange
    jest.spyOn(User, 'create').mockRejectedValue('error');
    const req = mockRequest();
    const res = mockResponse();
    req.body = payload;

    // act
    try {
        await register(req, res);
    } catch (error) {
        // assert
        expect(res.status).toHaveBeenCalledWith(500);
        expect(res.send).toHaveBeenCalledWith(
            expect.objectContaining({
                message: 'A server error occured'
            })
        );
    }
})

This way, the error will be thrown in the register function, and caught in the test, so the user variable will remain undefined.

  • Related