I'm asking this after a lot of research
I have these files
// connection.js
const mysql = require('mysql');
module.exports = mysql.getConnection();
// a.js
const connection = require('./connection');
function doQuery() {
const q = 'select * from table';
connection.query(q, (err, res) => {
... do some stuff
})
}
module.exports = doQuery;
When I what to do a test with jest (deleting unnecessary things to better read)
// test.js
const jest = require('jest');
const a = require('./a.js');
const connection = {
query: (query, cb) => cb('error', null),
};
jest.mock('./connection.js', () => connection);
test('testing something', () => {
expect(a.doQuery).to.be.true //this is just an example
});
I'm getting the next error
The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
Invalid variable access: connection
I tried moving the files in the same folder, with relative paths, absolute paths, moving the order of imports, and I really can't get it.
I really don't know how to fix this, also I'm migrating from proxyquire to jest that is the reason that I'm doing this and I can't use proxyquire anymore.
Yeah, well...
Gonna answer my very own question for the future.
The thing is that you cant use variables that are declared outside the jest.mock() function as I am doing.
The test file and the mock function should be something like this
Solution 1
jest.mock('./connection.js', () => ({
query: (query, cb) => cb('ee', null),
}));
Look that I'm not using any variables (const connection...) inside the jest.mock function
skyboyer
Also, I found out another solution thanks to #skyboyer.
Solution 2
Use names outside the jest.mock with the prefix mock*
const mockConnection = {
query: (query, cb) => cb('ee', null),
}
jest.mock('./connection.js',
Related
I have created my models using the sequelize-auto package and used them in my controllers
const sequelize = require('../database/db');
var models = require("../models/init-models").initModels(sequelize);
var User = models.User;
const controllerMethod = async (req,res,next) => {
//calls User.findAll() and returns the results
}
I have called the findAll function of User model in one of my controller methods
I want to test my controller method using Jest and want to mock the findAll function to return an empty object.
I have imported my models in the test file and mocked the findAll function as follows,
//inside test case
models.User.findAll = jest.fn().mockImplementation(() => {
return {}
});
const spy = jest.spyOn(models.User, "findAll")
await controllerMethod(req, res,next);
My question is when I run the test case it runs the actual findAll() function inside the controller instead of the mocked findAll()
i.e. findAll() returns actual data instead of {}
Any help would be greatly appreciated
Thanks in advance
Welcome to Stack Overflow. I think the issue with your code is some confusion on how spyOn works. Please see the documentation here specifically the following:
Note: By default, jest.spyOn also calls the spied method. This is different behavior from most other test libraries. If you want to overwrite the original function, you can use jest.spyOn(object, methodName).mockImplementation(() => customImplementation) or object[methodName] = jest.fn(() => customImplementation);
What this is telling you is that spyOn actual calls the original method, not the mock implementation.
The way I would do this (and assuming you do not need to assert on how findAll is called) is not use spyOn at all but create a dummy models that is returned from initModels, which has your dummy findAll method on it. Something like the following:
const mockModels = {
User: {
findAll: jest.fn(() => {})
}
};
// And then in your test - be careful as jest.mock is "hoisted" so you need to make sure mockModels has already been declared and assigned
test('blah', () => {
jest.mock("../models/init-models", () => ({
initModels: jest.fn(() => mockModels,
});
await controllerMethod(req, res, next) // ...etc
Managed to fix my issue until I come across a better solution.
I created a seperate models.js file and exported all my Models via that. Imported Models to my Controllers from the models.js file instead of const sequelize = require('../database/db'); var models = require("../models/init-models").initModels(sequelize);
models.js
const sequelize = require('../database/db');
var models = require("./init-models").initModels(sequelize);
module.exports.User= models.User;
module.exports.Instrument = models.Instrument;
module.exports.sequelize = sequelize; //exported this too since I have used sequelize.fn in some of my controllers
userController.js
//const sequelize = require('../database/db');
//var models = require("../models/init-models").initModels(sequelize);
//var User = models.User;
const {User,sequelize} = require('../service/models'); //imported models this way
const controllerMethod = async (req,res,next) => {
//calls await User.findAll() and returns the results
}
userController.test.js
const {controllerMethod} = require('../../../controllers/user');
const {User,sequelize} = require('../../../service/models');
//inside test case
jest.spyOn(User, "findAll").mockImplementation(() => {return Promise.resolve([])});
await controllerMethod(req, res,next);
in this way findAll mocks the way I wanted and returns the expected []
I am trying to read files with Node and then create a Object with the information that I extract from those files. I used the fs and path libs. I defined a empty Object outside the code that read the files, inside that code (where I use callbacks) the defined object get it's values modified, but outside it's value remain empty. Can someone help me?
Here is my code:
const path = require("path");
const fs = require("fs");
const dirPath = path.join(__dirname, "query");
let Query = {};
fs.readdir(dirPath, (error, files) => {
if (error) {
return console.log(error);
}
files.forEach((file) => {
const loaded = require(path.join(dirPath, file));
Object.entries(loaded).forEach(([key, value]) => {
Query[key] = value;
});
});
});
module.exports = {
Query,
};
When I ran console.log(Query) above the module.exports I got {} as answer, however running the same before the callback from fs.readdir ends return me the correct object.
I am not sure if the problem is non blocking IO or if I defined the object the wrong way.
fs.readdir is an asynchronous function, which means, the rest of the script will keep running without waiting the callback in readdir to finish.
Hence, the object Query will still print {} - it hasn't changed yet.
You want to continue your program only after reading the file.
One way to do this is to use readdirSync instead of readdir which is an synchronous function. Then, only when it is done, the program will continue.
Your code using readdirSync:
const path = require("path");
const fs = require("fs");
const dirPath = path.join(__dirname, "query");
let Query = {};
fs.readdirSync(dirPath).forEach((file) => {
const loaded = require(path.join(dirPath, file));
Object.entries(loaded).forEach(([key, value]) => {
Query[key] = value;
});
console.log(Query); // Should return the desired object
module.exports = {
Query,
};
am trying to write tests to test streams on my app, dealing with fs library with its createWriteStream function, so the stub i created is as follows:
writeStreamStub = sinon.stub()
onceStreamEventStub = sinon.stub()
endStreamStub = sinon.stub()
onStreamStub = sinon.stub()
createStreamStub = sinon.stub(fs, 'createWriteStream').returns({
write: writeStreamStub,
once: onceStreamEventStub,
end: endStreamStub,
on: onStreamStub
})
So now I can test for whether the functions are called and the returned functions are also called. But I am using the --coverage flag and the code of the callbacks of the returned functions is not covered, the write method is called inside a process.nextTick and I have no idea how to go about this. Is it possible to cover the whole code and the code inside the callbacks, and if so, how do I go about it. Thanks in advance.
N.B. The variables are globaly declared
If there's no cogent reason to use both sinon and jest, I'd recommend just using one library. If you decide to go with jest, here's a simple example. Assume you have a class like
const fs = require('fs');
module.exports = class FileWriter {
constructor() {
this.writer = fs.createWriteStream('./testfile.txt');
}
writeFile() {
process.nextTick(() => {
this.writeContent('hello world');
});
}
writeContent(content) {
this.writer.write(content);
this.writer.end();
}
};
and in your unit-test you want to mock the behaviour of all the used fs-functions (createWriteStream, writer, end in this case) and just check if they are called with the correct arguments. You could do this with something like this:
const fs = require('fs');
const FileWriter = require('./FileWriter');
// use this to have mocks for all of fs' functions (you could use jest.fn() instead as well)
jest.mock('fs');
describe('FileWriter', () => {
it('should write file with correct args', async () => {
const writeStub = jest.fn().mockReturnValue(true);
const endStub = jest.fn().mockReturnValue(true);
const writeStreamStub = fs.createWriteStream.mockReturnValue({
write: writeStub,
end: endStub,
});
const fileWriter = new FileWriter();
fileWriter.writeFile();
await waitForNextTick();
expect(writeStreamStub).toBeCalledWith('./testfile.txt');
expect(writeStub).toBeCalledWith('hello world');
expect(endStub).toHaveBeenCalled();
});
});
function waitForNextTick() {
return new Promise(resolve => process.nextTick(resolve));
}
Suppose I have a the following module, as database.js
const initOptions = {}
const pgp = require('pg-promise')(initOptions)
const config = require('../../config')
const db = pgp({
host: config.database.host,
port: config.database.port,
database: config.database.database,
user: config.database.user,
password: config.database.password
})
module.exports = db
And the following module as create.js
const db = require('./database')
function create (name) {
return new Promise((resolve, reject) => {
db.func('create', name)
.then(data => {
return resolve(data)
})
.catch(err => {
return reject(err)
})
})
}
module.exports = create
I'm trying to run a unit test on create.js that will test that db.func is called with 'create' as first argument and 'name' as the second, but without actually needing to set up a database connection (So tests can run offline).
From what I can gather, this is what libraries like sinon.JS can be used for, so I tried creating a test and stubbed the db object.
const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
chai.use(chaiAsPromised)
const sinon = require('sinon')
const expect = chai.expect
const create = require('./create.js')
describe('Test Module', () => {
it('should test stuff', () => {
const db = require('./database')
const dbStub = sinon.stub(db, 'func').callsFake(Promise.resolve(null))
expect(create('test').then).to.be.a('Function')
})
})
However, it fails with
TypeError: Cannot redefine property: func
Most likely due to my limited exposure to sinon...
How do I go about stubbing (or maybe I need to mock?) the db function so that I can test it an ensure db.func was called?
You can make the properties configurable by disabling locks with the no noLocking option in Initialization Options. This allows sinon to replace the properties:
const initOptions = { noLocking : true };
On a related note:
Your create function is creating an unnecessary promise wrapper, which is a promise anti-pattern. You should just return the result from db.func, which is a promise already:
function create(name) {
return db.func('create', name);
}
Also callsFake takes a function and you are giving it a promise. Use returns instead:
const dbStub = sinon.stub(db, 'func').returns(Promise.resolve(null))
I'm having trouble setting the noLocking option. The docs state it can be set after initialization, however if I set it with db.$config.options.noLocking = true, the same error occurs. However, if I set it in the database.js init options it works fine.
From the author of pg-promise...
It is because at that point the noLocking can only affect tasks and transactions. And since the db level of the protocol is initiated only once, setting noLocking after the library's initialization doesn't effect it.
I have just updated the documentation to clarify it:
This option is dynamic (can be set before or after initialization). However, changing it after the library's initialization will not affect Database objects that have already been created.
I am developing a "plugins" concept whereby I have a series of files, each containing a single function (a plugin). I would like to automatically load and execute these using promise.all().
Problem: each plugin function does not execute.
Here is my example plugin plugins/example.js:
"use strict";
exports = function() {
return new Promise(function(resolve, reject) {
console.log("Plugin running....");
setTimeout(resolve, 200, 'example plugin succeeded!');
});
};
From my app.js I then load all plugins using the require-all NPM module:
const plugins = require('require-all')(__dirname + '/plugins');
I then try to execute all as part of my promise chain:
return Promise.all([plugins]);
No logging takes place from the function. Interestingly when I log the contents of plugins, I see and empty object:
{
"example": {}
}
Can anyone advise why the example function is not being called?
One way of doing it would be something like following. Lets say that there is a plugins directory with files like pluginA.js, pluginB.js, ..., pluginZ.js. As you stated in your question, exported value from those plugins is always a function that will return a promise. I would create plugins/index.js that would export everything from those plugins like:
// plugins/index.js
'use strict'
const pluginA = require('./pluginA')
const pluginB = require('./pluginB')
...
const pluginZ = require('./pluginZ')
module.exports = [ pluginA, pluginB, ..., pluginZ ]
So then you could use this as following:
// foo.js
'use strict'
const _ = require('lodash')
const plugins = require('./plugins')
Promise.all(_.map(plugins, (fn) => fn()))
.then((data) => console.log(data))
.catch((err) => console.log(err))
The RequireAll plugin returns an object containing the plugin name as a key, and the import as the value, so you'll have to get the values and then call those functions to get the promises, and then you'd have an array of promises
const plugins = require('require-all')(__dirname + '/plugins');
var promises = Object.keys(plugins).map(function(key) {
return plugins[key]();
});
Promise.all(promises).then(function() {
// all done
}).catch(function(err) {
// fail
});