This question is related to Using Promises to test Meteor - Mocha
Like Louis suggested, I have replicated the same issue in a smaller program, so that you can reproduce this. And in this one too Mocha doesn't care about the assert statement. The catch block of the promises gets this error.
/server/main.js
import { Meteor } from 'meteor/meteor';
export const myCollection = new Mongo.Collection('mycollection');
export const addObject = function (id) {
myCollection.insert({
name: 'test ' + id
});
}
Meteor.publish('mycollection', function() {
return myCollection.find({});
});
/server/main.test.js
/**
* Created by enigma on 6/9/16.
*/
import { Meteor } from 'meteor/meteor';
import { PublicationCollector } from 'meteor/johanbrook:publication-collector';
import { Promise } from 'meteor/promise';
import { assert } from 'meteor/practicalmeteor:chai';
import { Random } from 'meteor/random';
import { addObject } from '../server/main.js';
if (Meteor.isServer) {
describe('test mocha promise', function() {
before(function() {
addObject(Random.id());
});
it('collects myCollection test', function() {
const collector = new PublicationCollector({ userId: Random.id()});
return new Promise(function(resolve) {
collector.collect('mycollection', function (collections) {
resolve(collections);
});
}).then(function(coll) {
chai.assert.notEqual(coll, null);
chai.assert.equal(coll, null);
}).catch(function(err) {
console.log('error:', err.stack);
});
});
});
}
console output
=> Meteor server restarted
I20160609-18:31:14.546(-5)? MochaRunner.runServerTests: Starting server side tests with run id GK3WqWY4Ln9u6vmsg
I20160609-18:31:14.598(-5)? error: AssertionError: expected { Object (mycollection) } to equal null
I20160609-18:31:14.598(-5)? at Function.assert.equal (packages/practicalmeteor_chai.js:2635:10)
I20160609-18:31:14.598(-5)? at test/main.test.js:25:29
I20160609-18:31:14.598(-5)? at /Users/enigma/.meteor/packages/promise/.0.6.7.1d67q83++os+web.browser+web.cordova/npm/node_modules/meteor-promise/fiber_pool.js:33:40
W20160609-18:31:14.607(-5)? (STDERR) MochaRunner.runServerTests: failures: 0
you need to either throw in the catch or remove the catch, so that mocha gets the error too. currently since you catch the error, the promise mocha gets is resolved.
Below was my old answer, before the question change
ps: it looks like I misunderstood what was a publish for meteor so the bellow answer is not really correct
The error you're encountering is because "mycollection" is not published
I is probably because Meteor.publish('mycollection'); is an async function, to the collection is not published yet when you test it.
you should do the publish in a before() before your test
Here is an example of how you can wait the publish to finish in a before
I read this in a discussion, this works for me, although some are discouraging use of 'done' callback with promises.
it('collects myCollection test', function(done) {
const collector = new PublicationCollector({ userId: Random.id()});
return new Promise(function(resolve) {
collector.collect('mycollection', function (collections) {
resolve(collections);
});
}).then(function(coll) {
chai.assert.notEqual(coll, null);
chai.assert.equal(coll, null);
done();
}).catch(function(err) {
done(err);
});
});
I use PublicationCollector like this:
it('should publish 2 documents', async () => {
const collector = new PublicationCollector({ 'userId': Random.id() });
const testPromise = new Promise((resolve, reject) => {
collector.collect('myDocuments',
(collections) => {
resolve(collections.myDocuments.length);
});
});
const result = await testPromise;
assert.equal(result, 1);
});
Adapted from here.
This test fails relatively gracefully.
Related
Trying to iterate through lines in a file using 'line-reader' in an async function that uses the "new Promise<..." approach. In a debugger, I can confirm that the resolve function is called, but the async function never returns. Kind of baffled by it.
Async function:
import * as lineReader from 'line-reader';
import * as fs from 'fs';
export async function iterateMyFileAsync(filename: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (!fs.existsSync(filename)) {
reject(new Error("file " + filename + " doesn't exist"));
return;
}
lineReader.eachLine(filename, (line, last) => {
// would want to do things, but issue repros without anything here
if (last) {
resolve()
}
});
});
}
Elsewhere:
await iterateMyFileAsync('myfile.txt');
In a debugger I can confirm that resolve() is definitely being called, but iterateMyFileAsync() never returns. Why might this happen?
Update after some digging and peers review comments
Your haved marked the function as async which will automatically convert your function to a promise. It is not nescessary to return a promise again.
The package line-reader seams not to work for some reason I can't explain. From npm it has been last published 3 years ago. check this codesanbox for failing exemples with line-reader eachLine and onpen APIs.
Try n-readlines npm package. I managed to make it work and it befits your usecase. Or, you can try node built in package readLine.
import lineByLine from 'n-readlines';
import * as fs from 'fs';
export async function iterateMyFileAsync(filename: string): Promise<void> {
if (!fs.existsSync(filename)) {
throw new Error('file ' + filename + " doesn't exist");
}
const liner = new lineByLine(filename);
let line;
while ((line = liner.next())) {
console.log('Line ', line.toString('ascii'));
}
return;
}
If your file is empty, your promise will not resolve.
Here is the spec of line-reader when the input file is empty:
should not call callback on empty file
it("should not call callback on empty file", function(done) {
lineReader.eachLine(emptyFilePath, function(line) {
assert.ok(false, "Empty file should not cause any callbacks");
}, function(err) {
assert.ok(!err);
done()
});
});
You should modify your code like this to handle empty file case:
import * as lineReader from 'line-reader';
import * as fs from 'fs';
export async function iterateMyFileAsync(filename: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (!fs.existsSync(filename)) {
reject(new Error("file " + filename + " doesn't exist"));
return;
}
lineReader.eachLine(filename, (line, last) => {
// would want to do things, but issue repros without anything here
},
(err) => {
if (err) {
reject(err)
return;
}
resolve()
});
});
}
Update: I solved this shortly after the question was posted, sorry for not closing this. It had to do with the debug workflow not working right, a breakpoint wasn't being hit when it should have been, leading me to think something impossible was happening.
I got two problems with this jest test:
Is it possible to define the Content collection only once instead of doing that inside of the test?
I do get this error:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
I don't see why my async code weren't stopped...
import resolvers from 'resolvers/'
import Db from 'lib/db'
const db = new Db()
describe('Resolver', () => {
let token
beforeAll(async () => {
await db.connect()
})
beforeEach(async () => {
token = 'string'
await db.dropDB()
})
afterAll(async () => {
await db.connection.close()
})
describe('articleGetContent()', () => {
test('should return dataset', async () => {
// SETUP
const Content = db.connection.collection('content')
const docs = [{
// some content...
}]
await Content.insertMany(docs)
// EXECUTE
const result = await resolvers.Query.articleGetContent({}, {
id: '123,
language: 'en'
}, {
token
})
// VERIFY
expect.assertions(1)
expect(result).toBeDefined()
})
})
})
resolver
import { articleGetContent } from '../models/article'
export default {
Query: {
articleGetContent: async (obj, { id }, { token }) => articleGetContent(id, token)
}
}
This is how my db class looks like
db.js
export default class Db {
constructor (uri, callback) {
const mongo = process.env.MONGO || 'mongodb://localhost:27017'
this.mongodb = process.env.MONGO_DB || 'testing'
this.gfs = null
this.connection = MongoClient.connect(mongo, { useNewUrlParser: true })
this.connected = false
return this
}
async connect (msg) {
if (!this.connected) {
try {
this.connection = await this.connection
this.connection = this.connection.db(this.mongodb)
this.gfs = new mongo.GridFSBucket(this.connection)
this.connected = true
} catch (err) {
console.error('mongo connection error', err)
}
}
return this
}
async disconnect () {
if (this.connected) {
try {
this.connection = await this.connection.close()
this.connected = false
} catch (err) {
console.error('mongo disconnection error', err)
}
}
}
async dropDB () {
const Content = this.connection.collection('content')
await Content.deleteMany({})
}
}
Related to the second question I hope you've found some issues on github about it.
In general, the issue is described in the debug log.
Jest works with promises, as a result, you shouldn't leave any async operations in any status except resolved.
In your case, you have your DB connection opened so you need to implement another method disconnect for your DB class, this link to docs will help you, but I guess you have it already as it's not the full db.js file ( I see some custom method dropDB. Main idea here is to have it in afterAll hook:
afterAll(() => db.disconnect());
Great example at the bottom of the page
What about the first question, it really depends on what you are doing in your method dropDB. If you're running method for dropping collection, you could store the reference to this collection somewhere outside and use it as it will automatically create the new one, but it would be great to see this method.
Additionally, your async test was created in a wrong way, you could read more here for example in my Update. You need to run this function in the beginning of the test: expect.assertions(number)
expect.assertions(number) verifies that a certain number of assertions
are called during a test. This is often useful when testing
asynchronous code, in order to make sure that assertions in a callback
actually got called.
I’m new to node and mongo after 15 years of VB6 and MySql. I’m sure this is not what my final program will use but I need to get a basic understanding of how to call a function in another module and get results back.
I want a module to have a function to open a DB, find in a collection and return the results. I may want to add a couple more functions in that module for other collections too. For now I need it as simple as possible, I can add error handlers, etc later. I been on this for days trying different methods, module.exports={… around the function and with out it, .send, return all with no luck. I understand it’s async so the program may have passed the display point before the data is there.
Here’s what I’ve tried with Mongo running a database of db1 with a collection of col1.
Db1.js
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinCol1 : function funk1(req, res) {
MongoClient.connect("mongodb://localhost:27017/db1", function (err,db) {
if (err) {
return console.dir(err);
}
var collection = db.collection('col1');
collection.find().toArray(function (err, items) {
console.log(items);
// res.send(items);
}
);
});
}
};
app.js
a=require('./db1');
b=a.FindinCol1();
console.log(b);
Console.log(items) works when the 'FindinCol1' calls but not console.log(b)(returns 'undefined') so I'm not getting the return or I'm pasted it by the time is returns. I’ve read dozens of post and watched dozens of videos but I'm still stuck at this point. Any help would be greatly appreciated.
As mentioned in another answer, this code is asynchronous, you can't simply return the value you want down the chain of callbacks (nested functions). You need to expose some interface that lets you signal the calling code once you have the value desired (hence, calling them back, or callback).
There is a callback example provided in another answer, but there is an alternative option definitely worth exploring: promises.
Instead of a callback function you call with the desired results, the module returns a promise that can enter two states, fulfilled or rejected. The calling code waits for the promise to enter one of these two states, the appropriate function being called when it does. The module triggers the state change by resolveing or rejecting. Anyways, here is an example using promises:
Db1.js:
// db1.js
var MongoClient = require('mongodb').MongoClient;
/*
node.js has native support for promises in recent versions.
If you are using an older version there are several libraries available:
bluebird, rsvp, Q. I'll use rsvp here as I'm familiar with it.
*/
var Promise = require('rsvp').Promise;
module.exports = {
FindinCol1: function() {
return new Promise(function(resolve, reject) {
MongoClient.connect('mongodb://localhost:27017/db1', function(err, db) {
if (err) {
reject(err);
} else {
resolve(db);
}
}
}).then(function(db) {
return new Promise(function(resolve, reject) {
var collection = db.collection('col1');
collection.find().toArray(function(err, items) {
if (err) {
reject(err);
} else {
console.log(items);
resolve(items);
}
});
});
});
}
};
// app.js
var db = require('./db1');
db.FindinCol1().then(function(items) {
console.info('The promise was fulfilled with items!', items);
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Now, more up to date versions of the node.js mongodb driver have native support for promises, you don't have to do any work to wrap callbacks in promises like above. This is a much better example if you are using an up to date driver:
// db1.js
var MongoClient = require('mongodb').MongoClient;
module.exports = {
FindinCol1: function() {
return MongoClient.connect('mongodb://localhost:27017/db1').then(function(db) {
var collection = db.collection('col1');
return collection.find().toArray();
}).then(function(items) {
console.log(items);
return items;
});
}
};
// app.js
var db = require('./db1');
db.FindinCol1().then(function(items) {
console.info('The promise was fulfilled with items!', items);
}, function(err) {
console.error('The promise was rejected', err, err.stack);
});
Promises provide an excellent method for asynchronous control flow, I highly recommend spending some time familiarizing yourself with them.
Yes, this is an async code and with a return you will get the MongoClient object or nothing, based on where you put.
You should use a callback parameter:
module.exports = {
FindinCol1 : function funk1(callback) {
MongoClient.connect("mongodb://localhost:27017/db1", function (err,db) {
if (err) {
return console.dir(err);
}
var collection = db.collection('col1');
collection.find().toArray(function (err, items) {
console.log(items);
return callback(items);
});
});
}
};
Pass a callback function to FindinCol1:
a.FindinCol1(function(items) {
console.log(items);
});
I suggest you to check this article:
https://docs.nodejitsu.com/articles/getting-started/control-flow/what-are-callbacks
Using proxyquire, sinon, and mocha.
I am able to stub fetch on the first call of fetch. But on the second fetch call, which is recursive, I am not able to assert it. From the output, it looks like the assertion may run before the test finishes. You will see this with second fetch console out after assertion.
index.js
var fetch = require('node-fetch');
function a() {
console.log('function a runs');
fetch('https://www.google.com')
.then((e) => {
console.log('first fetch');
b();
})
.catch((e)=> {
console.log('error')
});
}
function b() {
fetch('https://www.google.com')
.then((e) => {
console.log('second fetch');
})
.catch((e)=> {
console.log('error')
});
}
a()
test:
describe('fetch test demo', ()=> {
it('fetch should of called twice', (done)=> {
fetchStub = sinon.stub();
fetchStub2 = sinon.stub();
fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
fetch.returns(Promise.all(promises));
proxy('../index', {
'node-fetch': fetch
});
fetch.should.have.been.callCount(2);
done()
});
});
fetch test demo
function a runs
1) fetch should of called twice
first fetch
second fetch
lifx alert test
- fetch should of called three times
when rain change is over 50%
- should run fetch twice
0 passing (78ms)
2 pending
1 failing
1) fetch test demo fetch should of called twice:
expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (/home/one/github/lifx-weather/foobar.js:5:3)
AssertionError: expected stub to have been called exactly twice, but it was called once
stub(https://www.google.com) => [Promise] { } at a (foobar.js:5:3)
at Context.it (test/bar.js:22:28)
Updated version
#dman, since you updated your test case I owe you an updated answer. Although rephrased, the scenario is still unorthodox - it seems like you want to ignore in a sense the 'law of gravity' even though you know it's right there in front of you.
I'll try to be as descriptive as possible. You have two functions which are doing async stuff by design. a() calls b() sequentially - by the way this is not recursion. Both functions do not notify their callers upon completion / failure, i.e. they are treated as fire-and-forget.
Now, let's have a look at your test scenario. You create 3 stubs. Two of them resolve to a string and one combining their execution using Promise.all(). Next, you proxy the 'node-fetch' module
proxy('./updated', {
'node-fetch': fetch
});
using the stub that returns the combined execution of stubs 1 & 2. Now, if you print out the resolved value of fetch in either function, you will see that instead of a string it's an array of stubs.
function a () {
console.log('function a runs');
fetch('http://localhost')
.then((e) => {
console.log('first fetch', e);
b();
})
.catch((e) => {
console.log('error');
});
}
Which I guess is not the intended output. But let's move over as this is not killing your test anyway. Next, you have added the assertion together with the done() statement.
fetch.should.have.been.callCount(2);
done();
The issue here is that whether you are using done() or not, the effect would be exactly the same. You are executing your scenario in sync mode. Of course in this case, the assertion will always fail. But the important thing here is to understand why.
So, let's rewrite your scenario to mimic the async nature of the behavior you want to validate.
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe('fetch test demo', () => {
it('fetch should of called twice', (done) => {
var fetchStub = sinon.stub();
var fetchStub2 = sinon.stub();
var fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [fetchStub, fetchStub2];
fetch.returns(Promise.all(promises));
proxy('./updated', {
'node-fetch': fetch
});
setTimeout(() => {
fetch.should.have.been.callCount(2);
done();
}, 10);
});
});
As you can see, the only change made was wrapping the assertion within a timer block. Nothing much - just wait for 10ms and then assert. Now the test passes as expected. Why?
Well, to me it's pretty straightforward. You want to test 2 sequentially executed async functions and still run your assertions in sync mode. That sounds cool, but it's not gonna happen :) So you have 2 options:
Have your functions notify callers upon completion and then run your assertions in truly async mode
Mimic the async nature of things using unorthodox techniques
Reply based on original test scenario
It can be done. I've re-factored your provided files a bit so that
can be executed.
index.js
const fetch = require('node-fetch');
const sendAlert = require('./alerts').sendAlert;
module.exports.init = function () {
return new Promise((resolve, reject) => {
fetch('https://localhost')
.then(function () {
sendAlert().then(() => {
resolve();
}).catch(
e => reject(e)
);
})
.catch(e => {
reject(e);
});
});
};
alerts.js
const fetch = require('node-fetch');
module.exports.sendAlert = function () {
return new Promise((resolve, reject) => {
fetch('https://localhost')
.then(function () {
resolve();
}).catch((e) => {
reject(e);
});
});
};
test.js
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
const proxy = require('proxyquire');
describe.only('lifx alert test', () => {
it('fetch should of called twice', (done) => {
var body = {
'hourly': {
data: [{
time: 1493413200,
icon: 'clear-day',
precipIntensity: 0,
precipProbability: 0,
ozone: 297.17
}]
}
};
var response = {
json: () => {
return body;
}
};
const fetchStub = sinon.stub();
fetchStub.returns(Promise.resolve(response));
fetchStub['#global'] = true;
var stubs = {
'node-fetch': fetchStub
};
const p1 = proxy('./index', stubs);
p1.init().then(() => {
try {
fetchStub.should.have.been.calledTwice;
done();
} catch (e) {
done(e);
}
}).catch((e) => done(e));
});
});
What you're trying to do though is a bit unorthodox when it comes to
good unit testing practices. Although proxyquire supports this
mode of stubbing through a feature called global overrides, it is
explained here why should anyone think twice before going down
this path.
In order to make your example pass the test, you just need to add an
extra attribute to the Sinon stub called #global and set it to
true. This flag overrides the require() caching mechanism and
uses the provided stub no matter which module is called from.
So, although what you're asking can be done I will have to agree with
the users that commented your question, that this should not be
adopted as a proper way of structuring your tests.
Here is also a alternative way to do this using Promise.all().
Note: this won't work if using fetch's json method and you need to pass data in the resolve() for logic on data. It will only pass in the stubs when resolved. However, it will assert the number of times called.
describe('fetch test demo', () => {
it('fetch should of called twice', () => {
let fetchStub = sinon.stub();
let fetchStub2 = sinon.stub();
let fetch = sinon.stub();
fetchStub.returns(Promise.resolve('hello'));
fetchStub2.returns(Promise.resolve('hi'));
var promises = [ fetchStub, fetchStub2 ]
var promise = Promise.all(promises);
fetch.returns(promise);
proxy('../foobar', { 'node-fetch': fetch });
return promise.then(() => {
fetch.should.have.callCount(2);
});
});
});
I have found another way to get things done.
May be this could work for someone.
describe('Parent', () => {
let array: any = [];
before(async () => {
array = await someAsyncDataFetchFunction();
asyncTests();
});
it('Dummy test to run before()',async () => {
expect(0).to.equal(0); // You can use this test to getting confirm whether data fetch is completed or not.
});
function asyncTests() {
array.forEach((currentValue: any) => {
describe('Child', async () => {
it('Test '+ currentValue ,() => {
expect(currentValue).to.equal(true);
})
})
});
}
});
That's how I achieved the assertion on every element of the array. (Array data is being fetch asynchronously).
I have a function that returns an ES6 promise for use in another external promise chain. The function scans for wifi networks using child_process.exec. The output from exec is sent via the callback to a synchronous parsing function that will return the output in a formatted object, and then resolve the outer promise:
var exec = require('child_process').exec;
function scan() {
return new Promise((resolve, reject) => {
// getCurrent returns a promise
getCurrent().then(network => {
exec('iwlist wlan0 scan', function(err, stdout, stderr) {
(err) ? reject(stderr) : resolve(parseOutput(stdout, network));
});
})
.catch(err => {
reject(err);
});
});
}
The problem is, I can't seem to get sinon stubs working properly to get the stubbed version of exec to be called. Currently I have something along the lines of:
var wifi = require('./wifi');
describe('#scan', function() {
it('Should scan and return networks object', function() {
var fakeNetwork = '(fake iwlist output goes here)';
var exec = sinon.stub(child_process, 'exec').yields(false, fakeNetwork);
var getCurrent = sinon.stub(wifi, 'getCurrent').returns(Promise.resolve('current-ssid'));
wifi.scan().then(function(networks) {
console.log(networks);
sinon.assert.calledOnce(getCurrent);
sinon.assert.calledOnce(exec);
});
getCurrent.restore();
exec.restore();
});
});
I have also tried stubbing like:
var getCurrent = sinon.stub(wifi, 'getCurrent', function() {
return Promise.resolve('current-ssid').then(network => {
exec('iwlist wlan0 scan', function(err, data) {
return(wifi.parseOutput(data, network));
});
});
});
The behavior seems to be that either exec never gets called, or more strangely, that the stubbed exec does get called, but the 'real' functions get called anyway.
With all of the stubs, all I am really "testing" is the parseOutput method, so should I just ditch testing the 'scan' function completely and only focus on the parse function (which would be simple to test), or is there another way I should be looking at this?
I believe I am just doing something obviously wrong, and have been hitting a wall here for several hours. I'm hoping someone has run into this before and can suggest an alternative/better approach.
you can use stub.yields([arg1, arg2, ...]) sinon stubs guide
here is my code
function getVersioniList() {
let versionList = new Promise((resolve, reject) => {
child_process.exec(cmd, function(error, stdout, stderr) {
if (error) {
reject(error);
}
resolve(stdout);
});
});
return versionList;
}
and unit test code as below
// i like the sandbox, or you can use sinon itself
sandbox = sinon.sandbox.create();
sandbox.stub(child_process, 'exec').yields(undefined, [1, 2, 3]);
assert.equal(await getVersioniList(), [1, 2, 3]);
finally, you can get versionList [1, 2, 3]