简体   繁体   中英

Testing promise chains vs async await [mocha/chai/sinon]

So I'm new to testing and have set up this basic test of a method that mocks the failure of a database call (sorry if my terminology isn't quite right)

I'm using sequelize, so Job is a model and findAndCountAll a related method.

it('Should throw a 500 error if accessing the DB fails', () => {
    sinon.stub(Job, 'findAndCountAll');
    Job.findAndCountAll.throws();

    const req = {
        query: {
            index: 0,
            limit: 10,
            orderField: 'createdAt',
            order: 'DESC'
        }
    };

    adminController.getJobs(req, {}, () => {}).then(result => {
            expect(result).to.be.an('error');
            expect(result).to.have.property('statusCode', 500);

            done();
        })

    Job.findAndCountAll.restore();
})

My problem is that most of my code is written using promise chaining:

exports.getJobs = (req, res, next) => {
    const index = req.query.index || 0;
    const limit = req.query.limit || 10;
    const orderField = req.query.orderField || 'createdAt';
    const order = req.query.orderDirection || 'DESC';
 
    Job.findAndCountAll({
        // ...arguments
    })
    .then(results => {
        res.status(200).json({ jobs: results.rows, total: results.count });
        return // Attempted to add a return statement to enter the .then() block in the test
    })
    .catch(err => {
        if(!err.statusCode) err.statusCode = 500;

        next(err);
        return err; // Attempted to return the error to enter the .then() block in the test
    });

This doesn't work, and my (unecessary) return statements don't help either.

However, rewriting the method using async await does work (see below). However I'd like to avoid rewriting all my code, and it would be nice to understand the difference here.

My best guess is that rather than getting the sinon stub to throw an error, I should have it reject the promise? I'm just not entirely sure whether that's along the right lines or not, or how to achieve it. I'm kind of stumbling round the docs not really knowing

Any help appreciated,

Thanks,

Nick

exports.getJobs = async(req, res, next) => {
    const index = req.query.index || 0;
    const limit = req.query.limit || 10;
    const orderField = req.query.orderField || 'createdAt';
    const order = req.query.orderDirection || 'DESC';

    try {
        const results = await Job.findAndCountAll({ //...params });

        // ... 

        res.status(200).json({ jobs: results.rows, total: results.count });
        return;
    } catch(err) {
        if(!err.statusCode) err.statusCode = 500;
        next(err);
        return err;
    } 
};

So I think I found the answer:

The stub in sinon needs to return a rejected promise, rather than throwing an error:

sinon.stub(Job, 'findAndCountAll');
Job.findAndCountAll.rejects();

Afaik this is because you can't really throw an error in async code.

And the Promise chain in the method you're testing (in my case 'getJobs'), needs to return that promise.

So instead of

  Job.findAndCountAll({
            // ...arguments
        })
        .then(results => {
            res.status(200).json({...});
            return;
        })
        .catch(err => {
            if(!err.statusCode) err.statusCode = 500;

            next(err);
            return err;
        });

Use

   const results = Job.findAndCountAll({
            // ...arguments
        })
        .then(results => {
            res.status(200).json({...});
            return;
        })
        .catch(err => {
            if(!err.statusCode) err.statusCode = 500;

            next(err);
            return err;
        });
   return results;

Also, the async function in the test needs to either be returned, or awaited, so that mocha knows to wait. Using done() didn't work for me:

const result = await adminController.getJobs(req, {}, () => {});

expect(result).to.be.an('error');
expect(result).to.have.property('statusCode', 500);

Job.findAndCountAll.restore();

Hope that helps someone

**edit: as mentioned below, I forgot to pass done as an argument, which is why that method didn't work

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM