Ndx
Ndx

Reputation: 647

How to mock a knex function used in a route

I have this function that configure knex by environment

const knexConnection = () => {
   const config = require('./connection')[environment];
   return knex(config) 
}

I use this function in my route.js

module.exports = (app) => {
    app.get("/test", (req,res)=>{
            knexConnection().raw("SELECT NOW() as time").then(result => {
                const time = _.get(result.rows[0],'time')
                res.send(time);
            }).catch(err => throw(err))
        })

 }

my test file for the route.js

const sinon = require("sinon");
const chai = require("chai");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;

    const myStub = sandbox.stub().resolves("Query executed")
     const route = mock('../routes', {'../../knexConntection':knexConnection : { raw: myStub }}})

        route(app)

        chai.request(app)
            .get('/test')
            .set('content-type', 'application/x-www-form-urlencoded')
            .end((err, res) => {
                if (err) done(err);
                expect(myStub).to.have.been.called;
                expect(res.status).to.equal(200)
                done();
            })

When i execute the test file, the knexConnection.raw is stubbed and it shows the current time. and the test fails. it says the stub was never called.

I've been trying for days and it still hasnt work. any idea how to stub knex query?

UPDATE

After struggling with it for hours, I figured the stub get skipped because the app get instantiated before the stub. so the stub never get loaded.

My server structure has this structure.

-- server.js

//...all server stuff
//load all modeles routes using route
route(app)

here is my index.js as I dynamically load all route in server app.

var fs = require("fs");

module.exports = app => {
  fs.readdirSync(__dirname).forEach(file => {
    if (file == "index.js") return;

    const name = file.substr(0, file.indexOf("."));
    require("./" + name)(app);
  });
};

My mock still is being skipped and app get called first.

Upvotes: 1

Views: 2205

Answers (1)

jperl
jperl

Reputation: 5112

You can't change raw as knexConnection is a function not an object.

knexConnection().raw(...).then(...)

That is, it is a function that returns an object which has a raw function on it.

Besides, we might as well stub knexConnection while we're at it. So we would have control over what raw is.

const promise = sinon.stub().resolves("Query executed")
const knexConnection = sinon.stub().returns({
   raw: promise
})

Just one more thing, I've used Mocha. And to pass the stub from beforeEach to it, I use this.currentTest (in beforeEach) and this.test (in it). See the comments.

This made my tests passed:

// Import the dependencies for testing
const chai = require('chai');
const chaiHttp = require('chai-http');
const app = require('../server');
const route = require('../route');

const sinon = require("sinon");
const mock = require('proxyquire')
const httpStatus = require('http-status');
const expect = chai.expect;

chai.use(chaiHttp);
chai.should();

describe("test routes", () => {

  beforeEach(function() {

    const promise = sinon.stub().resolves("Query executed")
    // runs before all tests in this block
    const knexConnection = sinon.stub().returns({
        raw: promise
    })

    this.currentTest.myStub = promise //so as to access this in 'it' with this.test.myStub

    // warning : {'./knex': { knexConnection : knexConnection }} would replace knexConnection in route file
// with an object { knexConnection : knexConnection } causing the test to fail.
// Instead, you should write {'./knex': knexConnection}
    const route = mock('../route', {'./knex': knexConnection})

    route(app)
  });

  it("should call myStub", function(done) {
      var myStub = this.test.myStub;
        chai.request(app)
        .get('/test')
        .set('content-type', 'application/x-www-form-urlencoded')
        .end((err, res) => {
            if (err) done(err);
            sinon.assert.called(myStub);
            done();
        })
  })

  it("should have 'Query executed' as text", function(done) {
      var myStub = this.test.myStub;
        chai.request(app)
        .get('/test')
        .set('content-type', 'application/x-www-form-urlencoded')
        .end((err, res) => {
            if (err) done(err);
            sinon.assert.match(res.text, "Query executed")
            done();
        })
  })

    it("should have 200 as status", (done) => {
        chai.request(app)
        .get('/test')
        .set('content-type', 'application/x-www-form-urlencoded')
        .end((err, res) => {
            if (err) done(err);
            expect(res.status).to.equal(200)
            done();
        })
  })  
})

The route file:

const knexConnection = require('./knex.js');

module.exports = (app) => {
    app.get("/test", (req,res)=>{
            knexConnection().raw("SELECT NOW() as time").then(result => {
                res.send(result);
            }).catch(err => { throw(err) })
        })

 }

If you have any more questions, please do ask.

Upvotes: 1

Related Questions