I'm trying to write a test in JavaScript, the method I'm testing makes 2 method calls (model.expandChildren() and view.update();)
// inside 'app' / 'RV.graph'
var expandChildren = function(){
return model.expandChildren().then(function(r) {
view.update(r);
});
};
I've tried to use Jasmine specs to write the test to spyOn both the view and model functions, but it appears you can only have 1 spy in a given test. It seems I'm missing something big here and that there should be a way to mock out multiple methods calls using spies since my function needs to make both of these calls.
I want my spec to be able to run the way it is below, but it currently only passes the first test (the first spy runs as expected), the second test fails because Jasmine is trying to run the actual function, not the spied function:
var model = GRAPH.model;
var view = GRAPH.view;
var app = RV.graph;
describe('#expandChildren', function() {
beforeEach(function() {
// first spy, all good
spyOn(model, 'expandChildren').and.callFake(function() {
return new Promise(function(resolve) {
resolve(testResponse);
});
});
// second spy doesn't work because Jasmine only allows 1
spyOn(view, 'update');
app.expandChildren();
});
// passing test
it('calls model.expandChildren', function() {
expect(model.expandChildren).toHaveBeenCalled();
});
// failing test that runs the REAL view.update method
it('calls view.update', function() {
expect(view.update).toHaveBeenCalled();
});
});
Is there a way to do this with Jasmine?
Remember that you are working with asynchronous calls. The first call is synchronous, so it is recorded, but the second one only happens later. Give yourself some control over when things happen. I commonly use a pattern like this:
describe('#expandChildren', function() {
var resolver;
it('calls model.expandChildren', function(done) {
spyOn(model, 'expandChildren').and.callFake(function() {
return new Promise(function(resolve) {
resolver = resolve;
});
});
spyOn(view, 'update');
app.expandChildren();
expect(model.expandChildren).toHaveBeenCalled();
expect(view.update).not.toHaveBeenCalled();
resolver();
done();
expect(view.update).toHaveBeenCalled();
});
});
This way, the spec will only be run after the promise has been resolved and done() has been called.
Related
I have a data provider which gets the data from UI. For getting the data from UI, I am using before hook to open url and perform the required operations. But withData and before are invoked at same time ; Hence dataprovider has 'undefined' value which leads to the failure.
describe('abcd', function(){
before(function(){
//get data
});
withData(data, function(value){
it('abccd', function(){
},)
});
});
How can I achieve to get data first from UI and then pass it to dataprovider?
3 things to check ...
First, make sure that you get the data either in a synchronous way or make before handle asynchronous code. Read about it here: Mocha Asynchronous Code
Second, I don't know how withData works, but you can nest your tests in a way that will make Mocha call withData after calling before.
Third, make sure that you use data in the right scope and not accidentally get a different one.
So with these suggestions your code might look something like:
describe('abcd', function() {
var data = null; //declare data in a scope usable by `before` and `withData` functions
before(function() {
// get data synchronously
data = 'some data';
// or...
//return a promise so the tests don't start before the promise resolves
return getData().then(function (someData) {
data = someData;
})
});
// nested tests that will start only after `before` function finished executing
describe('with data', function () {
withData(data, function(value) {
it('abccd', function() {
//test
});
});
});
});
Im trying to unit test a function that calls a promise...
Using Mocha, Sinon. I have a functional block like this:
myfile.js:
let OuterDependecy = require('mydep');
function TestFunction(callback) {
OuterDependency.PromiseFunction().then(response => {
//some logic here
}).catch(err => {callback(err)});
inside my test i have used proxyquire to mock the outerdependecy
testfile.js
let proxyquire = require('proxyquire');
let OuterDepStub = {};
let testingFunc = proxyquire('myfile.js', {'mydep': OuterDepStub});
... then inside my testing block
let stubCallback = function() {
console.log('Stub dubadub dub'); //note...i can use sinon.spy here instead
};
beforeEach(()=>{
OuterDependency.PromiseFunction = function(arg) {
return new Promise((resolve, reject)=>{
reject('BAD');
});
};
spy = sinon.spy(stubCallback);
});
my actual test now calls the main "testfunction"
it('Catches Errors, and calls back using error', done => {
TestFunction(stubCallback);
expect(spy).to.have.been.called;
done();
});
I see the stub being called (the console log, hence why i didnt want to use sinon.spy) but the spy is saying its not called. and unit test fails.
I believe this is probably due to a race condition of sorts where the promise is resolving after my test is run... is there anyway to delay the test until my promise is resolve.
i know in in angularjs promise testing, there was a way to "tick" the promise so it resolves when you want to. possible in nodejs?
is there anyway to delay the test until my promise is resolve.
As far as I understand your issue, yes, you should only call done() after the promise is settled. In order to do that,you need two things:
1- Enforce TestFunction to return a Promise, so you can wait until it resolves:
function TestFunction(callback) {
return OuterDependency.PromiseFunction().then(response => {
//some logic here
}).catch(err => { callback(err) });
}
2- Wait to that promise to settle, then call done.
it('Catches Errors, and calls back using error', done => {
TestFunction(stubCallback).then(() => {
expect(spy).to.have.been.called;
done();
})
});
now, our then block won't run until the catch block within TestFunction, so if the test works as expected (i.e. the catch block fires and the callback gets fired), the expectation and the done calls will always fire after the callback gets called.
I see the stub being called (the console log, hence why i didnt want to use sinon.spy) but the spy is saying its not called. and unit test fails.
That's because your expectation runs right after the TestFunction calls, without waiting for it to settle. However, it will get called lately, thus the console.log appears in the next spec.
Our tests are organized like this:
describe("description", sinon.test(function() {
const harness = this;
it("should do something", function() {
// do something with harness.spy, harness.mock, harness.stub
});
}));
When run, these tests are all failing with TypeError: harness.spy is not a function. I've added some logs and found that harness.spy exists and is a function before the function passed to it is called, but inside the function passed to it, harness.spy is undefined.
Any help understanding what is happening here would be much appreciated.
The problem is the order in which Mocha executes your code. Wrapping the callback to describe with sinon.test cannot work. That's because the callbacks to all describe finish executing before any of the tests in it even start executing. The way sinon.test works, it creates a new sandbox, instruments this with some of the methods of the sandbox (spy, stub, etc.), then calls its callback, and when the callback returns, sinon.test removes from this the methods that it added.
So necessarily, any of the setup performed by sinon.test wrapping a describe callback will be undone before any of the tests are run. Here's an example where I've put some console.log. You'll see both console.log statements execute before any test is run.
const sinon = require("sinon");
describe("description", sinon.test(function() {
const harness = this;
it("should do something", function() {
});
console.log("end of describe");
}));
console.log("outside");
You need to wrap the callbacks you pass to it, instead, like this:
const sinon = require("sinon");
describe("description", function() {
it("should do something", sinon.test(function() {
this.spy();
}));
});
console.log("outside");
If the lifetime of the sandbox created by sinon.test does not work for you, then you have to create your sandbox and clean it "manually", like this:
const sinon = require("sinon");
describe("description", function() {
let sandbox;
before(function () {
sandbox = sinon.sandbox.create();
});
after(function () {
sandbox.restore();
});
it("should do something", function() {
sandbox.spy();
});
it("should do something else", function() {
// This uses the same sandbox as the previous test.
sandbox.spy();
});
});
I've hit a very weird problem: I'm trying to make unit tests to achieve 100% testing coverage on my application.
And of course I wrote some tests for my controllers but it seems like there is no way to test anything async in Ember (2.4.0) using ember-cli.
I have a function in controller that does this:
readObject() {
this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1',obj.get('property2');
}.bind(this));
}
I'm writing a test that should cover this function.
test('action readObject', function (assert) {
const cont = this.subject();
cont.readObject();
assert.equal(cont.get('property1'), 'someValue);
});
Obivously, this assert wouldn't work because readObject() is async call but this isn't the root of the problem. The problem is that then a callback in this.store.findRecord is being executed - my controller is already destroyed! So I get "calling set on destroyed object" error there.
In other words - even if I wrap my function in a promise and reformat both functions like this:
readObject() {
return new Promise(function(resolve) {
this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1',obj.get('property2');
resolve();
}.bind(this));
}.bind(this));
}
and
test('action readObject', function (assert) {
const cont = this.subject();
cont.readObject().then(function() {
assert.equal(cont.get('property1'), 'someValue);
});
});
It wouldn't work, because after executing readObject() my controllers become immediately destroyed, not waiting for any callbacks.
So, it could be any async call instead of store.findRecord - it could be Ember.run.later, for example.
Does anybody had the same issue? I've read a lot of articles I can't believe that Ember with such a big community doesn't provide a way to make async unit tests.
If anyone has any clues - please give me a hint, cause I'm kinda lost here. At the moment I have two thoughts:
I'm making controllers wrong, Ember doesn't suppose any async operations inside of it. But even if I move async calls to services - I hit the same problem with writing unit tests for them.
I have to decompose my functions to
readObject() {
this.store.findRecord('myModel',1).then(this.actualReadObject.bind(this));
}
actualReadObject(obj) {
this.set('property1',obj.get('property2');
}
to have at least callbacks body covered with tests, but this means I never get 100% testing coverage in my app.
Thank you in advance for any clues. :)
I had a similar problem and looking at the QUnit API - async I solved it. Try out following:
// ... in your controller ...
readObject() {
return this.store.findRecord('myModel',1).then(function(obj) {
this.set('property1', obj.get('property2');
}.bind(this));
}
// ... in your tests, either with assert.async: ...
const done = assert.async(); // asynchronous test due to promises usage
Ember.run(function(){
subject.readObject().then(function(){
// once you reach this point you are sure your promise has been resolved
done();
});
});
// or without assert.async
let promise;
Ember.run(function(){
promise = subject.readObject();
});
return promise;
In a case of unit tests I do also mock other dependencies, for example: store.
this.subject({
property1: null,
store: {
findRecord(modelName, id){
assert.equal(modelName, "myModel1");
assert.equal(id, 1);
return new Ember.RSVP.Promise(function(resolve){
resolve(Ember.Object.create({ property2: "a simple or complex mock" }));
})
}
}
});
I am not sure about the second case (the one without assert.async). I think it would work too, because the test suite returns a promise. This gets recorgnized by QUnit that waits for the promise.
I copy my own solution here, cause code formatting in comments isn't too good.
test('async', function (assert) {
const cont = this.subject();
const myModel = cont.get('store').createRecord('myModel'); //
// Make store.findRecord sync
cont.set('store',{
findRecord(){
return { then : function(callback) { callback(myModel); } }
}
});
// Sync tests
assert.equal(cont.get('property2'), undefined);
cont.readObject(); // This line calls store.findRecord underneath
assert.equal(cont.get('property2'), true);
});
So, I just turned store.findRecord into a sync function and it runs perfect. And again - many thanks to Pavol for a clue. :)
I've read about the new warnings regarding Promises not returning from handlers and had a related question...
In some of my unit tests, I stub out the functionality of certain dependencies to return promises using Jasmine's spyOn function. So with this new change, I am seeing quite a few warnings when my tests run. I can of course disable the warnings, but I wondered if there was an improved approach I could use that would naturally get rid of such errors?
Example:
beforeEach(function (done) {
var formatter = new Formatter();
var promise = Promise.resolve(['1,000', '2.45']);
spyOn(formatter, 'format').and.returnValue(promise);
// internally calls formatter.format()
doStuff(formatter, [1000, 2.4567]).then(done);
// promise is not returned
});
Can add a full working plunker if that would be of use?
The issue is that the done callback of jasmine does not return anything, so when you do .then(done) you trigger bluebird's warning for a promise was created in a handler but none were returned from it.
I solved this by adding a new method to the Promise object:
var Promise = require('bluebird');
Promise.prototype.jasmineDone = function(done) {
return this.then(function() {
done();
return null;
}, function(err) {
done.fail(err);
return null;
});
};
Then I refactor my specs from (which triggers the warning):
it('should ...', function(done) {
doSomething().then(done, done.fail);
});
To:
it('should ...', function(done) {
doSomething().jasmineDone(done);
});