node.jspostgresqlmocha.jschaisinon

Can we use sinon.stub without using async function in the API


I'm running unit testcases using mocha chai and using Ssnon.stub method to mock my DB functions inside the API and am not using async function in my API. When I try to run testcase it's getting timed out and not returning the response.

If I add async to my API and its working fine. But I don't want to use async in my API.

Is there any way to achieve this by without using async function?

admin.js

app.get("/allcurrency/:id", (req, res) => {
  adminfunctions.getAllCurrency(req, function (data) {
    if (!data.error) {
      res.status(200).send(data);
    } else {
      res.status(500).send(data);
    }
    });
  });

adminfunctions.js

const db = require(globalObject.__lib + 'dbutil');
 getAllCurrency: function (request, response) {
    let sqlText = pgescape(
      `SELECT * FROM EMPLOYEE LIMIT 25`
    );
    db.sqlSelect(sqlText, function (data) {
      return response(data);
    });
  },

dbutil.js

sqlSelect: function (sqltext, qResult) {
    self.dbQuery(sqltext, function (result) {
      globalObject.logger.debug('SQL: ' + sqltext);
      globalObject.logger.debug('RESULT: ' + JSON.stringify(result));
 
      if (result && result.error && result.error === true) {
        return qResult(result);
      } else {
        if (typeof result.length == 'undefined') {
          var tableArray = [];
          result.rows.forEach(function (tRow) {
            tableArray.push(tRow);
          });
        } else {
          var tableArray = [];
          result.forEach(function (vresult) {
            if (vresult.command == 'SELECT') {
              vresult.rows.forEach(function (tRow) {
                tableArray.push(tRow);
              });
            }
          });
        }
        globalObject.logger.debug('TABLE ARRAY: ' + JSON.stringify(tableArray));
        return qResult(tableArray);
      }
    });
  },
  
  
  
  
  
  dbQuery: async function (sqltext, results) {
    let error;
    let client;
    try {
      client = await pool1.connect();
      client.on('error', (err) => clientErrorListener(err));
      const res = await client.query(sqltext);
      await client.query('COMMIT');
      await client.end();
      return results(res);
    } catch (err) {
      let jsonObj = new Object();
      jsonObj.error = true;
      jsonObj.errorMessage = err.message;
      jsonObj.errorStack = err.stack;
      error = jsonObj;
      logger.error('ERROR MESSAGE: --> ' + err.message);
      logger.error(
        'ERROR OCCURED IN LINE & POSITION: --> ' +
          err.line +
          ' : ' +
          err.position
      );
      logger.error('ERROR FULL MESSAGE: ' + err.stack);
      return results(error);
    } finally {
      if (error) {
        logger.error(error);
        // logger.error(sqltext)
      }

      // client.off("error", clientErrorListener);
      client.release(error);*emphasized text*
      client.connection.stream.end();
    }
  },

admin.test.js

it("getallcurrency api = success 200", (done) => {
  const getUserByIdStub = sinon
    .stub(dbutil, "sqlSelect")
    .returns({ id: 1, name: "Test User", email: "test@example.com" });

  chai
    .request(appInstance)
    .get("/allcurrency/1")
    .end((err, res) => {
      expect(res).to.have.status(200);
      done();
      getUserByIdStub.restore();
    });
});

It is not returning any response from the above code.

And its working with the below code, and I don't want to use async and await function here.

app.get("/allcurrency/:id", async (req, res) => {
    const res = await adminfunctions.getAllCurrency(req);
    res.status(200).send(res);*emphasized text*
  });

Kindly assist me on how to use it without using async here.


Solution

  • db.sqlSelect() method is not an async function, it accepts a callback function to pass the data to the caller. It would help to use sinon.stub().callsFake(fakeFunction) to make the stub call the provided fakeFunction when invoked.

    e.g.

    dbutil.js:

    const dbutil = {
        sqlSelect: function (sqltext, qResult) {
            throw new Error('Not implemented');
        },
    };
    
    module.exports = dbutil;
    

    adminfunctions.js:

    const db = require('./dbutil');
    
    const adminfunctions = {
        getAllCurrency: function (request, callback) {
            let sqlText = `SELECT * FROM EMPLOYEE LIMIT 25`;
            db.sqlSelect(sqlText, function (data) {
                callback(data);
            });
        },
    };
    
    module.exports = adminfunctions;
    

    app.js:

    const express = require('express');
    const adminfunctions = require('./adminfunctions');
    
    const app = express();
    
    app.get('/allcurrency/:id', (req, res) => {
        adminfunctions.getAllCurrency(req, function (data) {
            if (!data.error) {
                res.status(200).send(data);
            } else {
                res.status(500).send(data);
            }
        });
    });
    
    module.exports = app;
    

    admin.test.js:

    const sinon = require('sinon');
    const dbutil = require('./dbutil');
    const chai = require('chai');
    const chaiHTTP = require('chai-http');
    const app = require('./app');
    
    chai.use(chaiHTTP);
    const { expect } = chai;
    
    it('getallcurrency api = success 200', (done) => {
        sinon.stub(dbutil, 'sqlSelect').callsFake((sqlText, callback) => {
            callback({ id: 1, name: 'Test User', email: 'test@example.com' });
        });
    
        chai
            .request(app)
            .get('/allcurrency/1')
            .end((err, res) => {
                expect(res).to.have.status(200);
                expect(res.body).to.deep.equal({ id: 1, name: 'Test User', email: 'test@example.com' });
                done();
            });
    });
    

    Test result:

      √ getallcurrency api = success 200
    
      1 passing (16ms)
    
    -------------------|---------|----------|---------|---------|-------------------
    File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
    -------------------|---------|----------|---------|---------|-------------------
    All files          |   88.88 |       50 |      80 |   88.88 | 
     adminfunctions.js |     100 |      100 |     100 |     100 | 
     app.js            |   88.88 |       50 |     100 |   88.88 | 11
     dbutil.js         |   66.66 |      100 |       0 |   66.66 | 3
    -------------------|---------|----------|---------|---------|-------------------