Testing actions in ember.js with sinon and konacha - javascript

I have a simple route in my app like this:
Dash.PostsNewRoute = Em.Route.extend({
model: function() {
return this.store.createRecord('post');
},
actions: {
saveForm: function() {
this.modelFor('postsNew').save();
}
}
});
Here is the test I've written to test saveForm and ensure that it's been called:
...
context('create new post', function() {
beforeEach(function() {
...
});
it('calls submit on route', function() {
var mock;
mock = sinon.mock(testHelper.lookup('route', 'posts.new'));
mock.expects('actions.saveForm').once();
this.submitButton.click();
mock.verify();
mock.restore();
});
});
However, I get an error with this implementation:
Attempted to wrap undefined property actions.saveForm as function
If I change the route and it's test like this, it'll work:
// Moving the save out of action and call it
Dash.PostsNewRoute = Em.Route.extend({
model: function() {
this.store.createRecord('post');
},
save: function() {
this.modelFor('postsNew').save()
},
actions: {
saveForm: function() {
this.save();
}
}
});
The new test:
it('calls submit on route', function() {
var mock;
mock = sinon.mock(testHelper.lookup('route', 'posts.new'));
mock.expects('save').once();
this.submitButton.click();
mock.verify();
mock.restore();
});
This way the test will pass. Is it possible to test actions.saveForm directly? Is the limitation of sinon that prevents me from accessing actions.saveForm?

You could mock the actions hash and set expectation for it like so:
mock = sinon.mock(testHelper.lookup('controller', 'posts_new')._actions);
mock.expects('save').once();
this.submitButton.click();
mock.verify();
mock.restore();

Related

Jasmine - how to test functions that should only be called once (executed via a closure variable)

I'm using a closure to ensure that something is only called once:
var pageDOM = (function() {
var mounted = false
return {
initializePage: function() {
if (mounted == false) {
pageDOM.addBoxes();
mount = true
}
pageDOM.otherInitProcedures();
},
otherFunction: function() {
}
}
})();
I'm not sure what's the right way of thinking about unit testing pageDOM.initializePage. Jasmine specs are run in random order, and I think it's important to keep this for testing integrity (i.e., I would NOT want to impose order). This is my spec code:
describe("pageDOM", function() {
describe("initializePage", function() {
beforeEach(function() {
spyOn(pageDOM, "addBoxes")
spyOn(pageDOM, "otherInitProcedures")
})
describe("calling initializePage first time", function() {
beforeEach(function() {
pageDOM.initializePage();
})
it("should call both functions", function() {
expect(pageDOM.otherInitProcedures).toHaveBeenCalled()
expect(pageDOM.addBoxes).toHaveBeenCalled()
})
describe("calling initializePage again", function() {
beforeEach(function() {
pageDOM.initializePage();
})
it("should only call otherInitProcedures", function() {
expect(pageDOM.otherInitProcedures).toHaveBeenCalled()
expect(pageDOM.addBoxes).not.toHaveBeenCalled()
})
})
})
})
})
The problem is that if the specs don't run in order, then both will fail. What's a way to test this, or should I even try to test this?
I would assign the spies to variables and reset the spies in an afterEach hook.
Something like this (follow the !! in the comments):
describe("pageDOM", function() {
describe("initializePage", function() {
// !! initialize these variables
let addBoxesSpy;
let otherInitProceduresSpy;
beforeEach(function() {
// !! assign the variables
addBoxesSpy = spyOn(pageDOM, "addBoxes")
otherInitProceduresSpy = spyOn(pageDOM, "otherInitProcedures")
})
describe("calling initializePage first time", function() {
beforeEach(function() {
pageDOM.initializePage();
})
it("should call both functions", function() {
expect(pageDOM.otherInitProcedures).toHaveBeenCalled()
expect(pageDOM.addBoxes).toHaveBeenCalled()
})
describe("calling initializePage again", function() {
beforeEach(function() {
pageDOM.initializePage();
})
it("should only call otherInitProcedures", function() {
expect(pageDOM.otherInitProcedures).toHaveBeenCalled()
expect(pageDOM.addBoxes).not.toHaveBeenCalled()
})
})
})
// !! Reset the spies in an afterEach
afterEach(() => {
addBoxesSpy.calls.reset();
otherInitProceduresSpy.calls.reset();
});
})
})
After resetting the calls to what you're spying on, order should not matter anymore.
So your "pageDOM" method is state full, so why use 2 times describe and set the call to "initializePage" method every time by hooking it in beforeEach, doesn't make sense. Instead you can do like this -
describe("pageDOM:initializePage", function() {
describe("calling initializePage first time", function() {
beforeEach(function() {
spyOn(pageDOM, "addBoxes");
spyOn(pageDOM, "otherInitProcedures");
})
it("should call both functions", function() {
pageDOM.initializePage();
expect(pageDOM.otherInitProcedures).toHaveBeenCalled()
expect(pageDOM.addBoxes).toHaveBeenCalled()
})
it("should only call otherInitProcedures", function() {
pageDOM.initializePage();
expect(pageDOM.otherInitProcedures).toHaveBeenCalled()
expect(pageDOM.addBoxes).not.toHaveBeenCalled()
})
})
})
Jasmine executes it blocks within a describe sequentially and you can get the desired checks as well. Working stackblitz link for you(ignore other testcases)
To make the code more testable, I would not initiate the function immediately.
var pageDOMConstructor = vfunction() {
var mounted = false
return {
initializePage: function() {
if (mounted == false) {
pageDOM.addBoxes();
mount = true
}
pageDOM.otherInitProcedures();
},
otherFunction: function() {
}
}
};
var pageDOM = pageDOMConstructor();
then you can test pageDOMConstructor easily.
To check how often something has been called you can use toHaveBeenCalledTimes
this is not quite complete and might need some small changes, its just to give you an idea of how to solve this:
describe("pageDOMConstructor", function () {
describe("initializePage", function () {
// setup variable, its a let because it will be reset before every test
let pageDom;
beforeEach(function () {
pageDom = pageDOMConstructor();
spyOn(pageDOM, "addBoxes");
spyOn(pageDOM, "otherInitProcedures");
});
it("should call both functions when calling initializePage first time", function () {
pageDOM.initializePage();
expect(pageDOM.otherInitProcedures).toHaveBeenCalledTimes(1);
expect(pageDOM.addBoxes).toHaveBeenCalledTimes(1);
});
it("should only call otherInitProcedures when calling initializePage again", function () {
pageDOM.initializePage();
// you could remove these two lines because they are in the other test
expect(pageDOM.otherInitProcedures).toHaveBeenCalledTimes(1);
expect(pageDOM.addBoxes).toHaveBeenCalledTimes(1);
pageDOM.initializePage();
expect(pageDOM.otherInitProcedures).toHaveBeenCalledTimes(1);
expect(pageDOM.addBoxes).not.toHaveBeenCalledTimes(1);
});
});
});
Each it should be treated like a separate test, and they should be completely self reliant - with some exceptions like beforeEach
When code is hard to test, it's often a sign that you could benefit from refactoring it a bit. I have found that testable code equals usable and flexible code in production.

One specific stub in sinon tests not working

I have a problem and can't seem to find the solution.
I'm writing Node.js code and using Sinon to unittest. I am testing a service and stubbing calls to the repository/database. Everything works despite for one repository method where the actual method is invoked instead of its stub. Here's the relevant code.
listingServiceTest.js
var chai = require('chai');
var expect = chai.expect;
var sinon = require('sinon');
var listingService = require('../listingService');
var listingRepository = require('../../repository/listingRepository');
var Listing = require('../../models/interfaceModel/listing');
//some testdata initialization
[..]
describe('saveListing(listing)', function() {
//everything works fine in here
[...]
});
describe('saveTags(listingID, tags)', function() {
beforeEach(function() {
sinon.stub(listingRepository, 'findTags', function(tags, callback) {
//This one works fine
var tags = [{
text: tags[0],
tagID: tags[0] + 'ID'
}];
setTimeout(function () {
callback (tags);
}, 10);
});
sinon.stub(listingRepository, 'saveTagCorrelation', function(listingID, tagID, done) {
setTimeout(function() {
//Here The actual Method is called instead of the stub ?!?!?
console.log('im saveTagCorrelation stub');
done();
}, 10);
});
sinon.stub(listingRepository, 'saveTag', function(tag, callback) {
//This one works fine
});
});
afterEach(function() {
listingRepository.findTags.restore();
listingRepository.saveTag.restore();
listingRepository.saveTagCorrelation.restore();
});
it("saveTags should execute this callback", function() {
listingService.saveTags(util.slugify(pTitle), pTags, function() {
//This obviously fails since the the stub isn't called
expect(listingRepository.saveTagCorrelation.getCall(0).args[0]).to.equal(util.slugify(pTitle));
expect(listingRepository.saveTagCorrelation.getCall(0).args[1]).to.equal(pTags[0] + 'ID');
expect(listingRepository.saveTag.getCall(0).args[0]).to.equal(pTags[1]);
expect(listingRepository.saveTagCorrelation.getCall(1).args[0]).to.equal(util.slugify(pTitle));
expect(listingRepository.saveTagCorrelation.getCall(1).args[1]).to.equal(pTags[1] + 'ID');
});
});
});
And here's the repo, that is supposed to be stubbed (I can see in the logs, that the actual method is logging, instead of the stub).
listingRepository.js:
var db = require ('../models/index');
var util = require ('../util');
module.exports = {
saveListing: [...],
findTags: [...],
saveTag: [...],
saveTagCorrelation: function (listingID, tagID){
//This is called instead of the stub
console.log('in actual Method');
var tagToSave;
tagToSave = db.tag_listing.build({
listingID: listingID,
tagID: tagID
});
tagToSave.save();
},
getListingByID: [...]
}
Am I blind? What am I missing?

callbacks/promises for the re-usable methods in Protractor

Should we write callbacks/promises for the re-usable methods in Page Object Pattern based testing in Protractor?
For example .. I have the below test code and Page Objects and its working fine without issues. But should I add callbacks for re-usable methods in page class?
describe('This is a test suite for Login cases',function(){
beforeEach(function() {
LoginPage.goHome();
LoginPage.doLogin();
});
afterEach(function() {
LoginPage.doLogout();
});
it('Scenario1_Login_VerifyFirstName',function(){
//Some Test step
});
Page Class:
var Login = {
PageElements: {
emailInput: element(by.css('.email')),
passwordInput: element(by.css('.password')),
loginForm: element(by.css('.form')),
logout: element(by.linkText('LOG OUT'))
},
goHome: function goHome() {
browser.get('/signin');
browser.driver.manage().window().maximize();
},
doLogin: function doLogin() {
this.PageElements.emailInput.sendKeys(UserName);
this.PageElements.passwordInput.sendKeys(Password);
this.PageElements.loginForm.submit();
},
doLogout: function doLogout() {
browser.wait(EC.elementToBeClickable(this.PageElements.profileLink));
this.PageElements.profileLink.click();
this.PageElements.logout.click();
}
};
module.exports = Login;
Yes you can.
By simply returning values or promises:
goHome: function() {
browser.get('/home');
return browser.getTitle();
},
And should resolve them on spec level inside "it" blocks like below:
it('Page should have a title', function() {
expect(Page.goHome()).toEqual('Home Page');
});

Protractor Page Object Model Javascript Sync

Page Object Model File:
var loginPage = function() {
console.log('Constructor~!');
};
loginPage.prototype = {
constructor: loginPage,
login: function () {
console.log('now I doing something');
browser.sleep(10000);
},
searchForElements: function () {
console.log('action that is after I login');
browser.sleep(20000);
},
addAnotherUser: function () {
console.log('now I doing something way into the future');
},
};
module.exports = loginPage;
Protractor Test File:
var loginPage = require("../pages/test.page.js");
describe('Creating new account', function(){
it('account will be created',function(){
loginPage = new loginPage();
loginPage.login();
console.log('On the page');
loginPage.searchForElements();
loginPage.addAnotherUser();
});
});
When I run this using protractor the console immediately prints out:
Constructor~!
now I doing something
On the page
action that is after I login
now I doing something way into the future
I would have thought that it wouldn't echo out the 'On the page' until after the browser has waited 10 seconds.
How can I sync up the browser and the javascript so that the searchForElements function doesn't run until the browser has completed the login function?

Testing requireJS methods async with Jasmine

I am trying to test a function that requires a module using jasmine and requirejs.
Here is a dummy code:
define("testModule", function() {
return 123;
});
var test = function() {
require(['testModule'], function(testModule) {
return testModule + 1;
});
}
describe("Async requirejs test", function() {
it("should works", function() {
expect(test()).toBe(124);
});
});
It fails, because it is an async method. How can I perform a test with it?
Note: I dont want to change my code, just my tests describe function.
For testing of an asynchronous stuff check runs(), waits() and waitsFor():
https://github.com/pivotal/jasmine/wiki/Asynchronous-specs
Though this way looks a bit ugly as for me, therefore you could also consider following options.
1. I'd recommend you to try jasmine.async that allows you to write asynchronous test cases in this way:
// Run an async expectation
async.it("did stuff", function(done) {
// Simulate async code:
setTimeout(function() {
expect(1).toBe(1);
// All async stuff done, and spec asserted
done();
});
});
2. Also you can run your tests inside require's callback:
require([
'testModule',
'anotherTestModule'
], function(testModule, anotherTestModule) {
describe('My Great Modules', function() {
describe('testModule', function() {
it('should be defined', function() {
expect(testModule).toBeDefined();
});
});
describe('anotherTestModule', function() {
it('should be defined', function() {
expect(anoterTestModule).toBeDefined();
});
});
});
});
3. Another point is I guess that this code is not working as you're expecting:
var test = function() {
require(['testModule'], function(testModule) {
return testModule + 1;
});
};
Because if you call test(), it won't return you testModule + 1.

Categories

Resources