- Details
- Written by: Stanko Milosev
- Category: Jasmine
- Hits: 5919
Recently I had a problem, I couldn't find a way to test private function which was called as a callback from another file.
Let's assume that we have one library like this:
/*global window*/ var executeFunctionToBeCalledAsCallBack; (function (ns) { "use strict"; var myLocalFunctionToBeCalledAsCallBack; function myCallBack(functionToBeCalledAsCallBack) { myLocalFunctionToBeCalledAsCallBack = functionToBeCalledAsCallBack; } function executeFunctionToBeCalledAsCallBack(){ myLocalFunctionToBeCalledAsCallBack(); } function MyCallbackTest() { return { myCallBack: myCallBack, executeFunctionToBeCalledAsCallBack: executeFunctionToBeCalledAsCallBack }; } ns.MyCallbackTest = MyCallbackTest; ns.myTest = new MyCallbackTest(); }(window));
Here notice first private variable myLocalFunctionToBeCalledAsCallBack, then notice method myCallback that it is assigning his parameter to local variable, and then execution of that variable which is expected to be function, in method executeFunctionToBeCalledAsCallBack. With the line ns.myTest = new MyCallbackTest(); I just created global variable which later I will use to execute my callbacks.
Now, let's use that my "library":
/*global document, window*/ (function (ns) { "use strict"; function myPrivateFunction() { document.querySelector("#myTest").innerHTML = "test"; } function ExecuteMyTest (myTest) { myTest.myCallBack(myPrivateFunction); myTest.executeFunctionToBeCalledAsCallBack(); } ns.ExecuteMyTest = ExecuteMyTest; }(window));
Here notice two lines:
myTest.myCallBack(myPrivateFunction);
myTest.executeFunctionToBeCalledAsCallBack();
With first line I will assign function to local, private, variable, with second I will execute that function.
HTML looks like this:
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="/sm.namespaces.js"></script> <script type="text/javascript" src="/sm.functions.js" defer></script> <script type="text/javascript" src="/sm.index.js" defer></script> </head> <body> <div id="myTest"></div> <button id="myButton" onClick="window.ExecuteMyTest(window.myTest)">Click Me!</button> </body> </html>
Now, we should write a test. Problem is that we can't access prvate function so easy. This is my test:
/*global describe, beforeEach, document, expect, it, window, myTest, jasmine*/ describe("call fake test", function () { "use strict"; var sut, myTest; beforeEach(function () { var myDiv = document.createElement('div'), functionToBeCalledAsCallBackTest; myDiv.setAttribute("id", "myTest"); document.body.appendChild(myDiv); sut = window.ExecuteMyTest; myTest = jasmine.createSpyObj("myTest", ["myCallBack", "executeFunctionToBeCalledAsCallBack"]); myTest.myCallBack.and.callFake(function (functionToBeCalledAsCallBack) { functionToBeCalledAsCallBackTest = functionToBeCalledAsCallBack; }); sut(myTest); functionToBeCalledAsCallBackTest(); }); it("div text should be test", function () { expect(document.querySelector("#myTest").innerHTML).toBe("test"); }); it("expect to be called", function () { expect(myTest.myCallBack).toHaveBeenCalled(); }); });
Here notice first part of code:
myTest = jasmine.createSpyObj("myTest", ["myCallBack", "executeFunctionToBeCalledAsCallBack"]);
myTest.myCallBack.and.callFake(function (functionToBeCalledAsCallBack) {
functionToBeCalledAsCallBackTest = functionToBeCalledAsCallBack;
});
First we created spy object, then we assigned parameter to our local variable, and then we executed that variable, expecting to be a function, with line:
functionToBeCalledAsCallBackTest();
Also, notice code:
myDiv = document.createElement('div'),
and then:
myDiv.setAttribute("id", "myTest");
document.body.appendChild(myDiv);
With part of code I created a div, which will be filled after execution of private function, I need that part to be able to test functionality...
Best would be if we look our tests with Karma coverage report to see how much we covered our code, just comment out line functionToBeCalledAsCallBackTest();, and check karma.
Here is how my package looks like:
{ "name": "jasmineExample", "version": "0.0.1", "devDependencies": { "karma": "^0.12.31", "karma-chrome-launcher": "^0.1.7", "karma-cli": "0.0.4", "karma-coverage": "^0.2.7", "karma-jasmine": "^0.3.5" } }
Karma config looks like this:
/*global module*/ module.exports = function(config) { "use strict"; config.set({ basePath: "..", plugins: ['karma-chrome-launcher', 'karma-coverage', 'karma-jasmine'], frameworks: ['jasmine'], files: [ 'sm.namespaces.js', 'sm.functions.js', 'sm.index.js', 'tests/spec/index.spec.js' ], // coverage reporter generates the coverage reporters: ['progress', 'coverage'], browsers: ['Chrome'], singleRun: true, preprocessors: { // source files, that you wanna generate coverage for // do not include tests or libraries // (these files will be instrumented by Istanbul) 'sm.index.js': ['coverage'], 'sm.functions.js': ['coverage'] }, // optionally, configure the reporter coverageReporter: { type : 'html', dir : 'coverage/' } }); };
Here notice line:
basePath: "..",
I had to add this line, otherwise if in files section I write something like:
'../sm.namespaces.js',
Karma will loose his path, and it will not be able to load css files anymore.
This my little example, you can see here, jasmine tests are here, everything together you can download from here, but you will have to install karma, go to subfolder tests (in my case that is C:\Users\pera.virtualOne\Downloads\jasmine\tests) execute following command:
npm install
and start Karma with:
karma start karma.conf.js
Now in upper folder you should see coverage folder, in my case here C:\Users\pera.virtualOne\Downloads\jasmine\coverage, go there and execute index.html.
- Details
- Written by: Stanko Milosev
- Category: Jasmine
- Hits: 4052
JS:
(function(ns) { var bar; function getBar() { return bar; } function setBar(value) { bar = value; } function MyTest() { bar = 5; return { getBar: getBar, setBar: setBar } } window.MyTest = MyTest; }(window.MyTest));
Then spec:
describe("call fake test", function () { var sut; beforeEach(function () { sut = new window.MyTest(); spyOn(sut, "getBar").and.callFake(function() { return 1001; }); }); it("should return 1001", function () { expect(sut.getBar()).toBe(1001); }) }); describe("create spy then force a call", function () { var sut, fetchedBar; beforeEach(function () { sut = new window.MyTest(); spyOn(sut, "getBar").and.callFake(function() { return 1001; }); sut.setBar(123); fetchedBar = sut.getBar(); }); it("tracks that the spy was called", function () { expect(sut.getBar).toHaveBeenCalled(); }) it("should return 1001", function () { expect(sut.getBar()).toBe(1001); }) });
With line:
fetchedBar = sut.getBar();
I am executing getBar method, then with line:
expect(sut.getBar).toHaveBeenCalled();
I am sure that getBar method is executed, and with line:
expect(sut.getBar()).toBe(1001);
we can see that fake value is returned.
Example download from here.
- Details
- Written by: Stanko Milosev
- Category: Jasmine
- Hits: 9467
Idea is to have a web page which takes time to load, but also to have timer which will be executed if loading page is stuck somewhere. Code which I am going to present is not the best one, of course, but point is just to show an example.
First we need web site which we will test, since code is small I will not present every file, just main JS which I am gonna test:
(function(ns) { function MyLoadFile() { var isLoadingStarted, contentURI = 'http://localhost/jasmineClock/file.html', loadingTimeOutHandle; function startLoading() { isLoadingStarted = true; setTimeout(function () { $( "#myWebSite" ).append( "<p>setTimeout</p>" ); isLoadingStarted = false; }, 15000); $.get( contentURI, function (data) { isLoadingStarted = false; $( "#myWebSite" ).append( data ); }); } function getIsLoadingStarted() { return isLoadingStarted; } return { isLoadingStarted: getIsLoadingStarted, startLoading: startLoading } } ns.MyLoadFile = MyLoadFile; }(window.loadFile))
Here notice method:
function getIsLoadingStarted() {
return isLoadingStarted;
}
I've completely forgot that variable "isLoadingStarted" cannot be seen "outside", instead you need "get" method ("getters" and "setters" in .NET world)
Now Jasmine test:
describe("when web site is loading", function() { var sut; beforeEach(function() { sut = new window.loadFile.MyLoadFile(); jasmine.clock().install(); sut.startLoading(); jasmine.clock().tick(15000); }); afterEach(function() { jasmine.clock().uninstall(); }); it("isLoadingStarted should be false", function() { expect(sut.isLoadingStarted()).toBe(false); }); });
Here first notice install and uninstall clock, and then tick. If you try to debug, you will see that Jasmine test will go directly to setTimeOut part of startLoading method, even though loading files is still going on, exactly how and why at this moment I don't know.
Jasmine test example you can see here, code which I am testing is here (you have to wait 15 sec to see text "setTimeout"), and whole example download from here.
- Details
- Written by: Stanko Milosev
- Category: Jasmine
- Hits: 4521
Just a short note to my self, window.onload must not be included in Jasmine test, otherwise it will not work.
For example, my index.js looks like:
window.onload = function () { window.loadFile.MyLoadFile(); }
namespaces.js:
window.loadFile = window.loadFile || {};
window.loadFile.js:
(function(ns) { function MyLoadFile() { var contentURI = 'http://localhost/jasmineClock/file.html', isLoadingStarted, loadingTimeOutHandle; isLoadingStarted = true; setTimeout(function () { $( "#myWebSite" ).append( "<p>setTimeout</p>" ); isLoadingStarted = false; }, 100); $.get( contentURI, function (data) { isLoadingStarted = false; $( "#myWebSite" ).append( data ); }); } ns.MyLoadFile = MyLoadFile; }(window.loadFile))
and in SpecRunner.html I didn't import the index.js:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner v2.1.3</title> <link rel="shortcut icon" type="image/png" href="/lib/jasmine-2.1.3/jasmine_favicon.png"> <link rel="stylesheet" href="/lib/jasmine-2.1.3/jasmine.css"> <script src="/lib/jasmine-2.1.3/jasmine.js"></script> <script src="/lib/jasmine-2.1.3/jasmine-html.js"></script> <script src="/lib/jasmine-2.1.3/boot.js"></script> <script type="text/javascript" src="/../jquery-2.1.3.js"></script> <script type="text/javascript" src="/../namespaces.js"></script> <script type="text/javascript" src="/../window.loadFile.index.js"></script> <!-- include spec files here... --> <script src="/spec/indexSpec.js"></script> </head> <body> </body> </html>