Promises in test cases in mocha and chai failing - javascript

I am writing a test case in mocha and chai to check if the file is not present it will create the file. Following is the test case :
context('if the valid message is supplied and file is not present in the app\'s logs folder', () => {
beforeEach((done) => {
setTimeout(() => {
fs.exists(filePath, (exists) => {
if (exists) {
fileFound = true;
} else {
fileFound = false;
}
});
done();
}, 100);
});
it('should indicate the file is not present in the app\'s log folder', () => {
expect(fileFound).to.be.false;
});
it('should create a new file in the app\'s log folder', () => {
expect(fileFound).to.be.true;
});
});
Let's day file is present in the folder, in that case first test case should fail. But the problem is, it is saying expected undefined to be false, rather than expected true to be false.

There's very little point in using promises here. Your API is callback-based, so you should use a callback test.
Like this:
it('should exist', (done) => {
fs.exists(filePath, (exists) => {
expect(exists).to.be.true;
done();
});
});
One thing to bear in mind, mostly unrelated to your issue, is that fs.exists is deprecated and you should use a different method like fs.access or fs.stat:
it('should exist', (done) => {
fs.access(filePath, (err) => {
expect(err).to.be.null;
done();
});
});
To address your post edit question, the problem here is that you are using setTimeout for no reason and calling done before fs.exists has a chance to finish.
The solution: get rid of the setTimeout and call done inside the fs.exists callback. You should also scope your fileFound variable at a place where it makes sense:
context('if the valid message is supplied and file is not present in the app\'s logs folder', () => {
let fileFound;
beforeEach((done) => {
fs.exists(filePath, (exists) => {
fileFound = exists;
done();
});
});
it('should indicate the file is not present in the app\'s log folder', () => {
expect(fileFound).to.be.false;
});
it('should create a new file in the app\'s log folder', () => {
expect(fileFound).to.be.true;
});
});

Related

How to execute functions synchronously to get the right output

I am making an application which reads a file and extracts the data line by line. Each line is then stored as an array element and that array is required as the final output.
const fs = require('fs');
const readline = require('readline');
var output_array=[];
const readInterface = readline.createInterface({
input: fs.createReadStream(inp_file),
console: false
});
readInterface.on('line', function(line) {
//code for pushing lines into output_array after doing conditional formatting
}
callback (null, output_array);
With this I am getting an empty array.
Though if I use 'setTimeout' then it's working fine
setTimeout(() => {
callback (null, output_array);
}, 2000);
How can I execute this synchronously without having to use 'setTimeout'?
You cannot execute asynchronous functions synchronously. But readline supports the close event, which is triggered when the inputfile is completely read, so you can call the callback there.
readInterface.on('close', () => {
callback(null, output_array);
});
Of course you could output the content on the close event, but you could also use a promise like this:
function readFile(fileName) {
return new Promise((resolve, reject) => {
const readInterface = readline.createInterface({
input: fs.createReadStream(fileName).on('error', error => {
// Failed to create stream so reject.
reject(error);
}),
});
const output = [];
readInterface.on('error', error => {
// Some error occurred on stream so reject.
reject(error);
});
readInterface.on('line', line => {
output.push(line);
});
readInterface.on('close', () => {
// Resolve the promise with the output.
resolve(output);
});
});
}
readFile(inp_file)
.then(response => {
callback(null, response);
})
.catch(error => {
// Take care of any errors.
});
I removed the console setting when calling createInterface() since it does not seem to be a valid option.
Make your callback from readline's close event handler. That way your callback will happen after all lines are read.
readInterface.on('close', function(line) {
callback (null, output_array);
}

Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL in the protractor

I am using a protractor and ECMAScript 6 to develop end to end tests. But when the test is finished I see the error:
Message:
Error: Timeout - Async callback was not invoked within the timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I tried all solutions that I find on the StackOverflow and GitHub. For example, an add jasmineNodsOption to the config file, add parameters done to the test function, add timeouts to the functions nothing help.
test.js
import { doubleClick, isDisplayed, waitToBeVisible } from '../page_objects/base_page';
import HairLossPage from '../page_objects/HairLossPage';
import CartComponent from '../page_objects/components/CartComponent';
/*global expect describe beforeAll it browser*/
describe('Hair-loss. Question pages', () => {
const heirLoss = new HairLossPage();
const cartComponent = new CartComponent();
beforeAll(() => {
browser.waitForAngularEnabled(false);
heirLoss.get();
});
it('Check the "CheckOut" button in the mini-cart with product', () => {
doubleClick(heirLoss.header.card);
waitToBeVisible(cartComponent.header.title);
expect(isDisplayed(cartComponent.header.title)).toBe(true);
expect(isDisplayed(cartComponent.content.shopHair)).toBe(true);
expect(isDisplayed(cartComponent.content.shopEd)).toBe(true);
expect(isDisplayed(cartComponent.content.shopSkin)).toBe(true);
expect(isDisplayed(cartComponent.content.shopDailyHealt)).toBe(true);
});
});
config.js
require("#babel/register");
require("protractor");
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['./src/tests/**.e2e.js'],
multiCapabilities: [{
browserName: 'chrome'
}],
//Global configuration
baseUrl: 'https:/facebook.com/',
//Allure configuration
/*global jasmine browser allure*/
onPrepare: () => {
browser.manage().window().maximize();
var AllureReporter = require('jasmine-allure-reporter');
jasmine.getEnv().addReporter(new AllureReporter());
jasmine.getEnv().afterEach((done) => {
if (done.status === "failed") {
browser.takeScreenshot().then((png) => {
allure.createAttachment('Screenshot', () => new Buffer(png, 'base64'), 'image/png')();
done();
});
}
});
}
};
I am thinking isDisplayed and waitToBeVisible are asynchronous and are getting stuck somewhere.
Try:
beforeAll(() => {
console.log('Before waiting for angular enabled');
browser.waitForAngularEnabled(false);
console.log('After waiting for angular enabled, before heirLoss.get');
heirLoss.get();
console.log('After heirLoss.get');
});
it('Check the "CheckOut" button in the mini-cart with product', () => {
console.log('before doubleClick');
doubleClick(heirLoss.header.card);
console.log('after doubleClick, before waitToBeVisible');
waitToBeVisible(cartComponent.header.title);
console.log('after waitToBeVisible, before first isDisplayed');
expect(isDisplayed(cartComponent.header.title)).toBe(true);
console.log('after first isDisplayed, before 2nd isDisplayed');
// keep adding console.log before and after these assertions and see where it gets stuck.
expect(isDisplayed(cartComponent.content.shopHair)).toBe(true);
expect(isDisplayed(cartComponent.content.shopEd)).toBe(true);
expect(isDisplayed(cartComponent.content.shopSkin)).toBe(true);
expect(isDisplayed(cartComponent.content.shopDailyHealt)).toBe(true);
});
Once you have a good idea of where it gets stuck, you can have a better idea of why it's getting stuck there.

How to share test cases in multiple suites with Jest?

I found that I have many repeated tests cases in multiple integration tests in a Node.js REST API. So for example, I test invalid requests for every endpoint where I expect an error to always have the same properties.
import { app } from 'server';
import * as request from 'supertest';
describe('Authentication tests', () => {
describe('POST /login', () => {
// other test cases
// describe('valid request should ...', () => {...})
describe('invalid requests with missing fields', () => {
let response = null;
beforeAll(async () => {
await request(app)
.post('/login')
.expect('Content-Type', 'application/json; charset=utf-8')
.field('email', 'invalid#test.com')
.then(res => {
response = res;
});
});
it('should return an invalid status code', () => {
expect(response.status).toBe(400);
});
it('should return a valid error schema', () => {
expect(typeof response.body).toBe('object');
expect(response.body).toHaveProperty('error');
expect(response.body.error).toHaveProperty('code');
expect(response.body.error).toHaveProperty('message');
});
it('should return an error with explicit message', () => {
expect(response.body.error).toHaveProperty('message');
});
});
});
});
Does Jest provide any way to create some share tests so I can encapsulate this error validation and declare it in other suite cases avoiding so much repetition?
You can encapsulate these tests into a function. The docs says:
Tests must be defined synchronously for Jest to be able to collect your tests.
For example:
function createInvalidRequestTests() {
describe('invalid request', () => {
let response;
beforeAll(async () => {
// simulate request of supertest
response = await Promise.resolve({ status: 400, body: { error: { code: 1, message: 'network error' } } });
});
it('should return an invalid status code', () => {
expect(response.status).toBe(400);
});
it('should return a valid error schema', () => {
expect(typeof response.body).toBe('object');
expect(response.body).toHaveProperty('error');
expect(response.body.error).toHaveProperty('code');
expect(response.body.error).toHaveProperty('message');
});
it('should return an error with explicit message', () => {
expect(response.body.error).toHaveProperty('message');
});
});
}
Then, you can use this function to define your tests. Jest test runner will collect and run these tests as usual
describe('Authentication tests', () => {
describe('POST /login', () => {
describe('valid request', () => {
it('should login correctly', () => {
expect(1).toBe(1);
});
});
createInvalidRequestTests();
});
describe('POST /register', () => {
describe('valid request', () => {
it('should register correctly', () => {
expect(2).toBe(2);
});
});
createInvalidRequestTests();
});
});
Unit test result:
PASS src/stackoverflow/58081822/index.spec.ts (9.622s)
Authentication tests
POST /login
valid request
✓ should login correctly (5ms)
invalid request
✓ should return an invalid status code
✓ should return a valid error schema (2ms)
✓ should return an error with explicit message
POST /register
valid request
✓ should register correctly (1ms)
invalid request
✓ should return an invalid status code (1ms)
✓ should return a valid error schema (2ms)
✓ should return an error with explicit message (1ms)
Test Suites: 1 passed, 1 total
Tests: 8 passed, 8 total
Snapshots: 0 total
Time: 12.053s, estimated 14s

JS Promises wait for the function and get the returned object

I'm trying to build some test helper functions that will help me to run some hooks with Mocha to test my GraphQL queries(just that you understand the context).
I want to do the following steps each time before I run the tests:
Connect to mongoDB with mongoose
Start the server (node)
Add some test data directly into the database (via mongoose models)
// this function returns back the server instance because I need it in the 'after' hook to be able to stop it after the tests execution.
export const startServer = () => {
mongoose.Promise = global.Promise;
mongoose.connect(MONGO_URI_TEST);
return mongoose.connection.once('open', () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return { schema: executableSchema };
})
);
return app.listen(9000, () => {
console.log('Server started!');
});
});
};
// now here's the before hook where the server is started
let server;
before( done => {
server = startServer(done); // here I need to wait
// some other async code that inserts test data into mongo
const user = new User({email: test#test.com});
user.save().then(() => {
// saved successfully
done();
})
});
I'm quite new in the JS world, how do you manage to wait (without async await syntax) until all the promises from startServer function are resolved and you get back the server instance and just after start inserting data into the database? It's a dummy question but also some links with a better explanation of this concept that I have here would be appreciated.
LE: The entire question has reduced to the followings:
const createServer = () => {
const server = app.listen(port, () => {
//callback body here
});
};
How to convert the callback to a Promise and at the same time return a valid server reference such that in the end I can do this:
const someOtherFunction = () => {
createServer().then(myValidServerInstance => {
//do something with that instance
}
}
TLDR: Some of the things about JS Promises were not clear for me, like for example the returning of the result via resolve() method.
So the right answer looks like this:
export const startServer = () => {
mongoose.Promise = global.Promise;
// first mistake was the use of callbacks rather than Promise approach offered by mongoose.connect
return mongoose.connect(MONGO_URI_TEST).then(() => {
console.log('Connected to MongoLab TEST instance.');
return createServer();
},
err => {
console.log('Error connecting to MongoLab TEST instance:', err);
});
};
The second one was returning directly the result of listen() before the operation get finished. So I've moved the code that is starting the server in another method and wrap the result of listen() into a promise and resolve the promise only when the server actually started listening.
const createServer = () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return {
schema: executableSchema,
context: { headers: req.headers},
};
})
);
return new Promise((resolve, reject) => {
const server = app.listen(9000, () => {
if (server) {
console.log('Server started on port 9000');
resolve(server);
} else {
reject();
}
});
});
};

How to assert stubbed fetch more than once

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).

Categories