1

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.

Ndx
  • 517
  • 2
  • 12
  • 29

1 Answers1

1

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.

felixmosh
  • 32,615
  • 9
  • 69
  • 88
jperl
  • 4,662
  • 2
  • 16
  • 29
  • the stub is still being skipped. it says its called 0 times. – Ndx Oct 05 '19 at 13:00
  • @Ndx With this exact configuration, my tests are passing. The problem might be lying elsewhere. Try to change expect(res.status).to.equal(200) to expect(res).to.equal({}) for debugging. The errors will display what's in res, specifically you'll want to check what's in the text property. You'll see what's the error. – jperl Oct 05 '19 at 14:05
  • the only thing im not using in your configuration test is the import, as Its not supported natively. But it shouldnt be an issue. And I still get res.body as the current timestamp, so my stub is still not working. – Ndx Oct 05 '19 at 14:28
  • 1
    @Ndx Indeed. I modified my route code like so: res.send(result) and I'm getting 'Query Executed' -> the text from my stub. Your problem lies with proxyquire. – jperl Oct 05 '19 at 14:50
  • How so? what could be a problem with proxyquire? On a side note, I have refactored my code to separate routes from controllers and havent yet successed in covering all tests https://stackoverflow.com/questions/58209419/stubbing-route-callback-render-route-status-not-found – Ndx Oct 05 '19 at 14:57
  • @Ndx make sure you have this: const promise = sinon.stub().resolves("Query executed") const knexConnection = sinon.stub().returns({ raw: promise }) this.currentTest.myStub = promise //so as to access this in 'it' with this.test.myStub const route = mock('../route', {'./knex': knexConnection}). The ./knex must reflect what's in the route file ('../route'). For example, const knexConnection = require('./knex.js'); – jperl Oct 05 '19 at 15:07
  • @Ndx And then 'result' in the then callback should be 'Query executed', not the time returned by the query (which won't be called at all). – jperl Oct 05 '19 at 15:12
  • I've copied all configuration and my stub is still being skipped. in my knex file, i have other function used. hence why im using the mock('../route', {'./knex': {knexConnection}}). And Im injecting all routes in app using the index.js file. Could it be possible since I execute route(app) which itself require the app to be loaded first. hence why i cant stub my function correctly? – Ndx Oct 05 '19 at 15:41
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/200444/discussion-between-jay-perl-and-ndx). – jperl Oct 05 '19 at 15:50