javascript unit testing variables and code not encapsulated inside functions - javascript

how to write unit test for variables in a angular js file.
fooFactory.spec.js
..
describe('test fooFactory', function(){
it('test if statement', function(){
expect(?).toBe(?);
// how to write a test to pass values to testVar
// testVar runs before I can assign value to it.
// even if I have setters and getters how can I retest the if statement
});
});
..
fooFactory.js
(function () {
angular.module('MyApp').factory('fooFactory', fooFactory);
function fooFactory(someOtherFile){
var testVar = someOtherFile.someOtherfunc;
if(testVar ){
// want to test this code. has 10 line of code
}
...
function foo(){
//does something and I can test this
}
...
return {
foo:foo
}
}
})();
how do i assign values to testVar before the if statement runs
if(testVar ){
// how do I test this code?
}
Should I encapsulate the entire if in a function and pass it through the return.
bar();
function bar(data){
if(data){
testVar = data;
}
if(testVar ){
// how do I test this code?
}
}
return {
foo: foo,
bar: bar
}
Is there a better way to do this.
Or should the js file have setters and getters in the first place. Thanks

you need to inject someOtherFile (which is, if I understand correctly a Service too) into fooFactory when creating it.
So have something like this in your test if you want to completly mock someOtherFile
describe('test fooFactory', function(){
var fooFactory;
beforeEach(function(){
fooFactory = new FooFactory(
{ someOtherfunc: function() { return true; } }
);
stateChangeCallback = $rootScope.$on.calls.first().args[1];
});
it('test if statement', function(){
expect(fooFactory).toBe(?);
// how to write a test to pass values to testVar
// testVar runs before I can assign value to it.
// even if I have setters and getters how can I retest the if statement
});
});
However, if you need someOtherFile and you don't want to mock all its responses, what you can do is use angular dependancy injection to inject this service and then only mock someOtherfunc on it. That will give something like this:
describe('test fooFactory', function(){
var fooFactory;
var someOtherFile;
beforeEach(inject(function (
_someOtherFile_
) {
someOtherFile = _someOtherFile_;
fooFactory = new FooFactory(
someOtherFile
);
}));
it('test if statement', function(){
spyOn(someOtherFile, 'someOtherfunc').and.returnValue(true);
expect(?).toBe(?);
// how to write a test to pass values to testVar
// testVar runs before I can assign value to it.
// even if I have setters and getters how can I retest the if statement
});
});

You cannot test functions/variable that are not accessible outside your factory.
The proper way of doing it would be to expose it. But be aware that you should not be exposing everything just to make it testable. You should really consider if adding a test for that function/variable will actually add value to your application.

Related

Angular Service becomes undefined when spyOn it

I have an Angular 1.6.6 application which I'm testing with Karma and Jasmine.
Given this code from controller:
$scope.undo = function () {
return $scope.isUndoDisabled() || $scope.undoAction();
};
$scope.isUndoDisabled = function () {
return HistoryService.isUndoDisabled();
};
I have been testing it with the following spec:
it('undoAction should not be called with default settings', function () {
var $scope = {};
var controller = $controller('PaintController', { $scope: $scope });
spyOn($scope, 'undoAction');
//spyOn(HistoryService, 'isUndoDisabled');
$scope.undo();
expect($scope.undoAction).not.toHaveBeenCalled();
});
And is passing the test, but when I uncomment the spyOn of HistoryService, the call HistoryService.isUndoDisabled() from $scope.isUndoDisabled returns undefined and then the test fails because:
Expected spy undoAction not to have been called.
Any idea about what's going on???? It seems like the spyOn is affecting to the code??
spyOn(...) is a shortcut for spyOn(...).and.stub(), not spyOn(...).and.callThrough(). When being spied this way, HistoryService.isUndoDisabled() returns undefined.
The proper way to test the unit is to isolate it from others. Since it is the controller that is tested, the service should be mocked or stubbed:
spyOn(HistoryService, 'isUndoDisabled').and.returnValue(true);
And then in another test:
spyOn(HistoryService, 'isUndoDisabled').and.returnValue(false);
I think if you want to call isUndoDisabled() from HistoryService, the function $scope.isUndoDisabled should be
$scope.isUndoDisabled = function () {
HistoryService.isUndoDisabled();
};
There shouldn't be a return in the body

Jasmine test private callback function

I know that calling private functions directly in unitTests is not a good practice and we must test the private code trough public methods.
I'm in a case that I don't know what to do to achieve what I want. I want to know if a callback function has been called from my interval. This is implemented in an angular controller.
function prepareInterval() {
self.callbacksData = [];
if(self.DynamicValuesList !== null) {
self.myPromise = $interval(callbackFunction, userInputInterval * 1000);
}
}
and my callback function only shows the data from the callbackFunction. I want to unitTest if that callbackFunction has been called but I can't.
I tried https://makandracards.com/makandra/32477-testing-settimeout-and-setinterval-with-jasmine
it('myUnitTest', function(){
//Prepare data
var controller = createController();
spyOn(controller, 'callbackFunction');
expect(controller.callbackFunction).not.toHaveBeenCalled();
});
The error that I'm getting is.
callbackFunction() method does not exist
EDIT: By the way I'm injecting the angular mock in the beforeEach function
I would take a slightly different approach here.
Obviously you don't want the callbackFunction itself to be exposed, so don't. Keep it private.
You do, however, return it as a value to your self instance.
self.myPromise = $interval(callbackFunction, userInputInterval * 1000);
So what you cán test, is that self.myPromise value. If that value is containing the interval, your interval has been set and thus you can be pretty sure your method has been called.
I expect that self object to be the controller, so you can just test the value of self.myPromise like this:
it('myUnitTest', function(){
//Prepare data
var controller = createController();
expect(controller.myPromise).toBe( /* undefined? */);
});
update
Just to test the interval value > 0:
You can try to refactor for testability. I'm not sure if the stringmatcher works on numbers though, and don't have time for a test myself now :)
function prepareInterval() {
self.callbacksData = [];
if(self.DynamicValuesList !== null) {
self.myPromise = $interval;
self.myPromise(callbackFunction, userInputInterval * 1000);
}
}
it('myUnitTest', function(){
//Prepare data
var controller = createController();
spyOn(controller, 'myPromise');
expect(controller.myPromise).toHaveBeenCalledWith(jasmine.any(Function), jasmine.stringMatching(/^[1-9][0-9]*$/));
});

Test a callback method's functionality in Jasmine

I have a service as following.
InvService(...){
this.getROItems = function(cb){
$http.get('url').success(cb);
}
}
One of the controllers which uses the above:
var roItems = [];
InvService.getROItems(function(res){
roItems = res.lts.items;
});
In Jasmine, I want to test that roItems are assigned the values from the response. How can I achieve this?
I'd recommend that you have separated tests for you service and for your controller. If you want to test that roItems was assigned, you need to test your controller. Then, you could mock your service since it is not relevant for the controller test and make it return whatever you want. You need something like this:
describe('my awesome test', function() {
it('my awesome test block',
inject(function(InvService, $controller) {
//This mocks your service with a fake implementation.
//Note that I mocked before the controller initialization.
spyOn(InvService, 'getROItems').and.callFake(function(cb){
var resultFake = {
lts: {
items: "whatever you want"
}
}
cb(resultFake);
});
//This initializes your controller and it will use the mocked
//implementation of your service
var myController = $controller("myControllerName");
//Here we make the assertio
expect(myController.roItems).toBe("whatever you want");
}
)
});

Qunit: Test leakage

I have a leakage problem when testing my jQuery plugin. The problem occurs when I want to mock out a value or function on a literal object.
Example:
test('Overwrite some default setting', function(){
$.fn.plugin.defaults.bar = 'foo';
});
test('Bar should be undefined', function(){
equals( $.fn.plugin.defaults.bar, undefined );
});
This test will fail because the first test added the 'bar' var to defaults. I fixed it with the following code but taking a copy of a copy doesn't look very elegant.
$(function(){
/*
* Trolley Button Base: Options.
*/
var defaults_copy = $.extend({}, $.fn.plugin.defaults );
var setdown = {
setup : function(){
$.fn.plugin.defaults = $.extend({}, defaults_copy);
},
teardown : function(){ }
};
module('Test leakage', setdown );
test('Overwrite some default setting', function(){
$.fn.plugin.defaults.bar = 'foo';
});
test('Bar should be undefined', function(){
equals( $.fn.plugin.defaults.bar, undefined );
});
})
Also if I have a few objects in the jQuery namespace it might become a little messy if I have to take multiple copies of each object. So was wondering does anybody have a better solution to 'reset' all objects?
This is by design for QUnit. At the end of each test, you should clean up any state changes you've made. I don't know of a way to automatically do that -- you have to write the code to undo the effects of any test code you've written, like this:
test('Overwrite some default setting', function(){
// test code
$.fn.plugin.defaults.bar = 'foo';
// cleanup code
delete $.fn.plugin.defaults.bar;
});
test('Bar should be undefined', function(){
equals( $.fn.plugin.defaults.bar, undefined );
});
To prevent issues with test order dependency and to isolate your unit tests fully, you will need to manually implement Test level setup and tear down functionality by creating a function for each and including it at the beginning and end of each of your test methods:
e.g.
$(document).ready(function () {
// Test Setup/TearDown
function codeUnderTestModuleTestSetup() {
//any setup needed
}
function resetDefaults() {
//code in here to reset defaults
}
function resetSomethingElse() {
//code in here to reset something else
}
function codeUnderTestModuleTestTearDown() {
resetDefaults();
resetSomethingElse();
}
//Tests
module('Your module title Test Harness');
test('FunctionUnderTest_Behaviour_ExpectedResult', 1, function () {
codeUnderTestModuleTestSetup();
//Arrange
//code removed
//Act
//Code removed
//Assert
//Code removed
codeUnderTestModuleTestTearDown();
});
}
You can manually implement module level and test run level setup and teardown functions too, if necessary.

Is there anyway to unit test javascript functions defined within a function?

I would just like to ask whether I would be able to unit test the code inside ExternalFunction within the document.ready? I have tried many things for a while now and still couldn't work out how, and am at my wits end.
$(document).ready(function () {
var originalExternalFunction = ExternalFunction;
ExternalFunction = function(context, param) {
// trying to unit test the stuff in here!
}
}
I'm unit testing using JsTestDriver. Test declaration is something like TestThisTest.prototype.test_this - function() {};
Thanks in advance.
Since, in your example, ExternalFunction is not declared within the scope of the function, it is global (or at least, in whatever scope it may have been defined in outside ready). You can therefore test it by calling it as a global.
The trouble is, in order to assign the function to ExternalFunction, you have to run ready (which you could run manually, if you need). This means that if you put any other functionality in ready, then no, it is not unit testable. If your example code is an accurate reflection of reality, then I suppose it is kinda testable.
The point of a construct like this, is to hide the inner function. If you don't wish to hide it, then Anon.'s suggestion of defining newExternalFunction in a more accessible scope is what you need.
If your function needs to be a closure using variables from within ready, you could define newExternalFunction thus:
var newExternalFunction;
$(document).ready(function () {
var originalExternalFunction = ExternalFunction;
newExternalFunction = function(context, param) {
// trying to unit test the stuff in here!
}
ExternalFunction = newExternalFunction;
}
You would still need to ensure that ready has run, prior to unit testing, but you wouldn't have to rely on ExternalFunction not being reset to originalExternalFunction.
You could do something like:
function newExternalFunction(context, param) {
//etc.
}
$(document).ready(function () {
var originalExternalFunction = ExternalFunction;
ExternalFunction = newExternalFunction;
}
Then it's relatively straightforward to run your unit tests on newExternalFunction.
Theoretically, you could do something like:
ExternalFunction = function() { }
ExecuteDocumentReady(); // implement a mock on $(document).ready(fn) to store the function, and then execute it here
ExternalFunction(fakeContext, fakeParam);
assert(fakeContext.foo == 12); // or whatever you need it to do
That being said, I'm not sure exactly how to do that in javascript.
You could use a closure to generate your callback function:
// create function to make your "extension" function
function createHookFunction(callback) {
// return a function
return function(context, param) {
var ret;
// // trying to unit test the stuff in here!
if (typeof callback == 'function') {
// if you want to trap the return value from callback,
// ret = callback.apply(...);
callback.apply(this, arguments);
}
return ret;
};
}
// your hook now becomes:
$(document).ready(function() {
ExternalFunction = createHookFunction(ExternalFunction);
});
// and your unit test becomes:
var funcToTest = createHookFunction();
funcToTest(testContext, testParam);
// And, you could even test that the callback itself gets called
function someTest() {
var testContext = {}, testParam='test';
var callbackCalled = false;
var funcToTest = createHookFunction(function(context, param) {
callbackCalled = (context === testContext) && (param === testParam);
});
return (funcToTest(testContext, testParam) == 'Expected Return') && callbackCalled;
}

Categories

Resources