I would like to unit test (mocha & chai) a pool connection (mysql) in my node.js app. I don't know if I'm using the tests in the correct way and, if so, why they are always qualified as "pending".
Giving the following code, how would I test it?
var pool = mysql.createPool({
connectionLimit: 100,
host: 'localhost',
user: 'userName',
password: 'userPass',
database: 'userDatabase',
debug: false
});
I have tried in many ways but it doesn't seem to work. Best I got was this:
describe("Database", function () {
describe("Connection", function () {
it("Should connect without an error", pool.getConnection(function (err, connection) {
expect(err).to.be.null;
})
);
})
});
Which would return, if credentials are correct:
Express server listening on port 8080
Database
Connection
- Should connect without an error
0 passing (15ms)
1 pending
And return, if credentials are incorrect:
Express server listening on port 8080
Database
Connection
- Should connect without an error
1) Uncaught error outside test suite
0 passing (23ms)
1 pending
1 failing
1) Database Connection Uncaught error outside test suite:
Uncaught AssertionError: expected [Error: ER_DBACCESS_DENIED_ERROR: Access denied for user 'userName'#'localhost' to database 'userDatabase'] to be null
Thank you in advance.
What you pass to it in the 2nd argument must be a function. Right now you are doing:
it("Should connect without an error", pool.getConnection(...))
pool.getConnection takes a callback so in all likelihood, it returns undefined. So it looks like this to Mocha:
it("Should connect without an error", undefined)
And this is a pending test because having undefined for the callback is the way to tell Mocha the test is pending. You need to wrap your call to pool.getConnection in a function:
it("Should connect without an error", function (done) {
pool.getConnection(function (err, connection) {
if (err) {
done(err); // Mocha will report the error passed here.
return;
}
// Any possible tests on `connection` go here...
done();
});
});
See testing asynchronous code in the mocha docs. Your it function should look similar to the below.
it('Should connect without an error', function (done) {
pool.getConnection(done);
});
Or, if you want to add assertions inside your callback do the following:
it('Should connect without an error', function (done) {
pool.getConnection((err, connection) => {
try {
expect(connection).to.not.be.null;
expect(connection).to.have.property('foo');
done();
} catch (error) {
done(error);
}
});
});
Note you should preferably test with promises because this allows you to run expect statements on the connection object without extra try/catch/done statements. For example, if pool.getConnection returns a promise, you can do:
it('Should connect without an error', function () {
return pool.getConnection(connection => {
expect(connection).to.have.property('foo');
// more assertions on `connection`
});
});
Also note that these are not "unit tests", but integration tests, as they are testing that two systems work together rather than just your application behaving as expected on its own.
Related
I'm quite new to node and express.
And have been trying to write test code using mocha, chai and chai-http.
Here's the part of source code.
const mongoose = require('mongoose'),
User = require('../../models/user');
const mongoUrl = 'mongodb://xxxxxxxxxxx';
describe('/test', function() {
before('connect', function() {
return mongoose.createConnection(mongoUrl);
});
beforeEach(async function(done) {
try {
await User.remove({}); // <-- This doesn't work
chai.request('http://localhost:3000')
.post('/api/test')
.send(something)
.end((err, res) => {
if (err) return done(err);
done();
});
} catch (error) {
done(error);
}
});
});
And I get the following error with "npm test"(nyc mocha --timeout 10000 test/**/*.js).
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I confirmed the database connection works properly from log.
And seems I get the timeout error with await User.remove({}).
I've also tried different methods such as a User.save()
But, I got the same error.
Do I need to do something special with database model and connection?
This is all pretty simple.
To avoid the error you must not use both done and async/await in Mocha at the same time. Either use async/await and remove both done as function parameter and done() call. Or use done. Then remove both async/await. See the example tests below for each case.
Use try/catch with async/await as you would normally use it with synchronous code.
Following are the most basic Mocha tests with both async/await and done approaches testing the same basic HTTP server endpoint.
This is async/await approach.
it('with async/await', async function() {
const res = await chai.request(server)
.get('/')
.send();
assert.equal(res.status, 200);
});
This is done approach.
it('with done & callbacks', (done) => {
chai.request(server)
.get('/')
.end((err, res) => {
assert.equal(res.status, 200);
done();
});
});
See the full test file snippet.
For working example additionally spin basic Express server as the tests counterpart in src/app.js.
See Chai HTTP plugin docs for more info on what you can do with request testing.
This is it.
I had the same problem and have not found a way to get any promises that involve mongoose working with Mocha/Chai.
What may help you is doing what I did and putting your mongoose code in a script so you can run it with node <scriptfile>.js. You can use that to confirm it's working properly by itself. In my test, the mongoose operation finished in less than a second. You can also call that file from another (non-test related) to confirm it executes properly and returns a promise. You can see from my example how to make sure you close db properly. Partial example:
...
db.close();
return new Promise((resolve) => {
db.on('disconnected', () => {
console.log('***************************************Mongoose CONNECTION TERMINATED');
resolve('user ready');
});
});
...
You may also find some clues by looking at the following issues here and here.
The work around that I did after wasting too much time trying to figure out this crazy behavior was to perform my mongoose needs in a route. I wrap each request that needs to use it in the end block of the extra chai.request... or use async. Example:
describe('something', () => {
it('should do something and change it back', async () => {
try {
// change user password
let re1 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
});
expect(re1.statusCode).to.equal(200);
// change password back since before hook not working
let re2 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
passwordNew: 'password',
passwordConfirm: 'password',
});
expect(re2.statusCode).to.equal(200);
} catch (error) {
// error stuff here
}
});
Note that using the try/catch syntax above will cause test that should normally fail to show passing and the results will be caught in the catch block. If you want to avoid that, just remove the try/catch.
How did you implement ./models/user? await only works if User.remove() returns a promise, not if it expects a callback. I would add debug information to your User.remove() function to see where it gets stuck.
So I've tried looking up this bug, but I can't seem to find an answer that caters to my bug. I'm using mocha and chai-http to test some API's. I'm just hitting the endpoints using their corresponding RESTFUL methods (POST, GET, PUT) and checking the response (really straight forward). The problem is, individually my test suites work (if I were to run them one at a time), but when I run them using my gulp command ... I get "callback not a function" for some of the test cases (the ones in the if hook if you're familiar with mocha)
Here's the error I'm getting:
Uncaught TypeError: callback.apply is not a function
at Immediate.<anonymous> (node_modules/mongoose/lib/model.js:3683:16)
at Immediate._onImmediate (node_modules/mongoose/node_modules/mquery/lib/utils.js:137:16)
Here's the structure of my directory that my test cases reside:
test
backend
assignments_tests.js
application_tests.js
courses_test.js
& I have a gulp file with the following:
// Set the environment variable for the test cases to point to production
gulp.task('set-testing-env', function() {
return process.env.NODE_ENV = 'production';
})
// gulp task for backend
gulp.task('test-backend', ['set-testing-env'], function() {
return gulp.src('test/backend/**/*.js', {read: false})
.pipe(mocha({
reporter: 'spec',
timeout: 60000
}))
.on('error', function(err) {
// console.log(err);
process.exit();
});
});
& finally a sample of my test case:
describe("testing GET" ....
before(function(done) {
... setting up stuff here and calling done ...
})
describe("get courses information and courses offered", function() {
it("gets a courses information", function(done) {
const endpoint = test.endpoint + "/courseInfo/" + test.course
chai.request(app)
.get(endpoint)
.end(function(err, res) {
expect(err, "Error hitting endpoint").to.be.null;
expect(res, "Expected data in json format").to.be.json;
expect(res, "Expected status to be 200").to.have.status(200);
expect(res.body, "Expected course info!").to.have.length(1);
expect(res.body[0],"Missing keys").to.have.all.keys(courseInfo);
done();
})
})
})
describe("testing POST" ...
before(function(done) {
... setting up and calling done ...
})
describe(...
it(...)
})
})
I'm not too sure why I'm getting a callback not a function error. :(
Any help would be much appreciated!
Probably in some part of code that you didn't include here you are calling a Mongoose method passing something like too many objects as parameters, like in this question:
Async.each throwing error
One of those objects gets interpreted as a function but cannot be called as such.
This is a common problem but you didn;t include any code that runs Mongoose methods so it's hard to tell if that is the issue here.
The tests run well when using the real database, but after I mocked it with mockgoose, it reports an error
Uncaught Error: Error connecting to database: failed to connect to [yankiserver-test:27017]
at null.
(/Users/twer/GDrive/2Dev/node/yankiServer/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/server.js:555:74)
at emit (events.js:118:17)
...
My project is based on MEAN.JS. I have added the mocking part to test.js.
var mongoose = require('mongoose');
var mockgoose = require('mockgoose');
console.log('mocking goose');
mockgoose(mongoose);
It works well for saving model directly:
it('should be able to save without problems', function(done) {
return article.save(function(err) {
should.not.exist(err);
done();
});
});
But when I try to hit database through express
it('should be able to get a list of articles', function(done) {
request(app).get('/articles/')
.end(function(req, res) {
done();
});
});
It throws the above error.
Note: The connection has been established before running the test based on my debuging.
I do the clean-up in an after() call before any other describe. If all tests pass, the clean-up will do the job. But if any test fails, the clean-up code will receive an err: [Error: no open connections].
I think the assertion in the callback of mongodb throws an error cause the connection closed.
That make me confusing:
First, I think the callback of mongodb is the right place to put some assertions;
Second, the assertions will throw error when failed, and cause connection closes;
Finally, the clean-up will failed due to connection closed.
So, what else should I do to make clean-up to do its job even the assertion failed?
I have made a sample code below:
var mongo = require('mongoskin')
, should = require('should')
;
describe('mongo', function() {
var db;
before(function() {
console.log('before');
db = mongo.db('devstack.local:27017/test')
});
after(function(done) {
console.log('after');
db.dropDatabase(function(err) {
should.not.exist(err);// [Error: no open connections]
db.close(done);
});
});
describe('close', function() {
it('should count!=0', function(done) {
db.collection('empty').count(function(err, count) {
count.should.not.equal(0); // use an empty collection to make sure this fail
done();
});
})
})
})
Here's an hypothesis: the connection never happens.
When I run your test suite with:
db = mongo.db('nonexistent:3333/test')
instead of the address you have, I can completely reproduce your error. Note that:
count.should.not.equal(0); fails because count is undefined, not because any of the framework defined by the should module is called.
If I transform the test so that it checks err :
it('should count!=0', function(done) {
db.collection('empty').count(function(err, count) {
should.not.exist(err); // <<< This is where it fails now!
count.should.not.equal(0); // use an empty collection to make sure this fail
done();
});
});
Then the test fails at should.not.exist(err) and err is:
[Error: failed to connect to [nonexistent:3333]]
A couple of thoughts:
Always check err in your callbacks.
In the before callback which establishes the database connection, perform at least one operation which is guaranteed to fail if the connection is not established. You'd want an operation which is as inexpensive to perform as possible. I don't know Mongo very well but this seems to do the trick:
before(function (done) {
db = mongo.db(<put address here>, {safe: true});
db.open(function (err) {
should.not.exist(err);
done();
});
});
This way Mocha will detect the failure right away.
In my node application I'm using mocha to test my code. While calling many asynchronous functions using mocha, I'm getting timeout error (Error: timeout of 2000ms exceeded.). How can I resolve this?
var module = require('../lib/myModule');
var should = require('chai').should();
describe('Testing Module', function() {
it('Save Data', function(done) {
this.timeout(15000);
var data = {
a: 'aa',
b: 'bb'
};
module.save(data, function(err, res) {
should.not.exist(err);
done();
});
});
it('Get Data By Id', function(done) {
var id = "28ca9";
module.get(id, function(err, res) {
console.log(res);
should.not.exist(err);
done();
});
});
});
You can either set the timeout when running your test:
mocha --timeout 15000
Or you can set the timeout for each suite or each test programmatically:
describe('...', function(){
this.timeout(15000);
it('...', function(done){
this.timeout(15000);
setTimeout(done, 15000);
});
});
For more info see the docs.
I find that the "solution" of just increasing the timeouts obscures what's really going on here, which is either
Your code and/or network calls are way too slow (should be sub 100 ms for a good user experience)
The assertions (tests) are failing and something is swallowing the errors before Mocha is able to act on them.
You usually encounter #2 when Mocha doesn't receive assertion errors from a callback. This is caused by some other code swallowing the exception further up the stack. The right way of dealing with this is to fix the code and not swallow the error.
When external code swallows your errors
In case it's a library function that you are unable to modify, you need to catch the assertion error and pass it onto Mocha yourself. You do this by wrapping your assertion callback in a try/catch block and pass any exceptions to the done handler.
it('should not fail', function (done) { // Pass reference here!
i_swallow_errors(function (err, result) {
try { // boilerplate to be able to get the assert failures
assert.ok(true);
assert.equal(result, 'bar');
done();
} catch (error) {
done(error);
}
});
});
This boilerplate can of course be extracted into some utility function to make the test a little more pleasing to the eye:
it('should not fail', function (done) { // Pass reference here!
i_swallow_errors(handleError(done, function (err, result) {
assert.equal(result, 'bar');
}));
});
// reusable boilerplate to be able to get the assert failures
function handleError(done, fn) {
try {
fn();
done();
} catch (error) {
done(error);
}
}
Speeding up network tests
Other than that I suggest you pick up the advice on starting to use test stubs for network calls to make tests pass without having to rely on a functioning network. Using Mocha, Chai and Sinon the tests might look something like this
describe('api tests normally involving network calls', function() {
beforeEach: function () {
this.xhr = sinon.useFakeXMLHttpRequest();
var requests = this.requests = [];
this.xhr.onCreate = function (xhr) {
requests.push(xhr);
};
},
afterEach: function () {
this.xhr.restore();
}
it("should fetch comments from server", function () {
var callback = sinon.spy();
myLib.getCommentsFor("/some/article", callback);
assertEquals(1, this.requests.length);
this.requests[0].respond(200, { "Content-Type": "application/json" },
'[{ "id": 12, "comment": "Hey there" }]');
expect(callback.calledWith([{ id: 12, comment: "Hey there" }])).to.be.true;
});
});
See Sinon's nise docs for more info.
If you are using arrow functions:
it('should do something', async () => {
// do your testing
}).timeout(15000)
A little late but someone can use this in future...You can increase your test timeout by updating scripts in your package.json with the following:
"scripts": {
"test": "test --timeout 10000" //Adjust to a value you need
}
Run your tests using the command test
For me the problem was actually the describe function,
which when provided an arrow function, causes mocha to miss the
timeout, and behave not consistently. (Using ES6)
since no promise was rejected I was getting this error all the time for different tests that were failing inside the describe block
so this how it looks when not working properly:
describe('test', () => {
assert(...)
})
and this works using the anonymous function
describe('test', function() {
assert(...)
})
Hope it helps someone, my configuration for the above:
(nodejs: 8.4.0, npm: 5.3.0, mocha: 3.3.0)
My issue was not sending the response back, so it was hanging. If you are using express make sure that res.send(data), res.json(data) or whatever the api method you wanna use is executed for the route you are testing.
Make sure to resolve/reject the promises used in the test cases, be it spies or stubs make sure they resolve/reject.