Jasmine mocking a constructor within a function - javascript

I get an error when running Jasmine tests: 'ReferenceError: JSZip is not defined'. Here's my controller:
$scope.makeZip = function() {
var zip = new JSZip();
zip.file('myPhoto.txt', 'Hello World);
return 'foo' + zip.generate();
};
And test:
it('should make a zip with correct name', function() {
var zipFileName = scope.makeZip();
expect(zipFileName).toMatch('foo');
});
My guess is that I need to mock JSZip constructor somehow: I tried inserting the following at the beginning of the test:
spyOn(window, 'JSZip').andReturn(null);
but then I get 'JSZip() method does not exist'.
I thought this is similar problem to Jasmine - How to spy on a function call within a function?, but I couldn't find a right way to fix the issue.
Any ideas? Thanks for all help!

JSZip is defined on the window. You should not have to pass it into the constructor. In your jasmine page (where you holster your specs), reference jszip.js. If you want, you could create a service..
your angular module.value("JSZip",JSZip);
then pass it into your controller. This is only useful if you wanted to mock JSZip, though.

Solved it! I didn't actually want to test JSZip, only to check whether a function makeZip is called correctly.
it('should make a zip with correct name', function() {
window.JSZip = function() {
this.file = function() {};
this.generate = function() {
return 'bar';
};
};
var zipFileName = scope.makeZip();
expect(zipFileName).toMatch('foo'); //makeZip should return string containing 'foo'
});

Related

JS/Sinon - beforeEach(function(done) not called

Just starting with Sinon, Mocha, and Chai JS. After upgrading UnderscoreJS version from 1.4.4 to 1.9.1, I had to update how my project was using template function.
Previously, _.template function was used in this way -
var myTemplate = _.template("<p><%= name %></p>", {name: 'Joe Doe'});
New Way,
// `myTemplate` here is a function!
var myTemplate = _.template("<p><%= name %></p>");
// Now let's pass in the data for the template.
myTemplate({name: 'Joe Doe'}); // it returns: "<p>Joe Doe</p>"
However, this change caused a lot of the existing test cases to fail. Below mentioned is one of the test cases I need help with -
const sinonVar = require('sinon');
describe('testVar', function() {
var sanboxVar;
var ss = requirejs('Path to JS library');
var testVar;
beforeEach(function(done) {
console.log("Not Called"); // Never printed on console
done();
sanboxVar = sinonVar.sanbox.create();
});
it('some text here', function() {
console.log("sanboxVar: " + sanboxVar); //Printed on console as 'undefined'
ss.id = sanboxVar.stub();
});
});
On "npm test", I see the error -
testVar
some text here:
TypeError: Cannot read property 'stub' of undefined
at Context.<anonymous> (testPage.js) - "This is pointing to sanboxVar not defined"
I think for some reason, beforeEach method is not getting called and hence the Sandoval variable is not getting initiated.
Any help would be appreciated.

Mocking ngResource in Angular unit tests

I have an ngResourceMockFactory which looks like this:
(function() {
'use strict';
angular.module('app')
.factory('NgResourceMock', ngResourceMockFactory)
;
ngResourceMockFactory.$inject = [];
function ngResourceMockFactory() {
function NgResourceMock() {
var context = this;
context.$promise.then = function() {
context.prototype.$promise.then.apply(context, arguments);
};
context.$promise.finally = function() {
context.prototype.$promise.finally.apply(context, arguments);
};
}
NgResourceMock.prototype.$promise = {
then: function(onSuccess, onError) {
this.$promise.onSuccess = onSuccess;
this.$promise.onError = onError;
},
finally: function(onComplete) {
this.$promise.onComplete = onComplete;
}
};
return NgResourceMock;
}
})();
I inject this into my tests in a beforeEach like so:
beforeEach(inject(function(NgResourceMock) {
ngResourceMock = new NgResourceMock();
}));
then I use it like this:
describe('initiateWorkflow function', function() {
beforeEach(function() {
vm.player = {id: 123};
spyOn(dataService, 'initiateWorkflow').and.returnValue(ngResourceMock);
vm.initiateWorkflow();
});
it('should call dataService.initiateWorkflow', function() {
expect(dataService.initiateWorkflow).toHaveBeenCalledWith({playerId: vm.player.id}, {});
});
});
but I keep seeing the following error:
TypeError: 'undefined' is not an object (evaluating 'context.prototype.$promise')
This leads me to believe that something is wrong with my ngResourceMockFactory, but I'm not sure what it is.
Don't know if this can be of any help, but if you are trying to evaluate asynchronous operations in your tests, you may want to use the done() method in Jasmine.
As per their documentation:
beforeEach(function(done) {
setTimeout(function() {
value = 0;
done();
}, 1);
});
by passing done as a parameter of the beforeEach callback, any test run after the before each will wait until the done() function has been called.
Source: Jasmine (Asynchronous Support section).
Hope this helps.
Here is the solution to your problem.
The error TypeError: 'undefined' is not an object (evaluating 'context.prototype.$promise') is caused when you try to invoke the promise object before invoking the function into which it is defined or into which your parent function is defined.
Here the returnValue(ngResourceMock) is directly calling into the function without the context and parameters need to be defined.
Therefore you can try to add another beforeEach statement like
beforeEach(angular.mock.module(app));
to load your app module
Here may be the same concept related to your problem another link here.
Hope it may help you a bit.

Monitoring of a library with sinon

I have the following code to test using sinon:
var req = new MultiPartUpload({
client: client,
objectName: "/" + obj.func.destPath(),
stream: obj.outStream,
headers: headers
}, function (err, body) {
obj.debug('uploaded' + body);
});
I have to test the creation of this object. How can I do it? I have tried with:
var MultiPartUpload = require('knox-mpu');
var stub = sinon.createStubInstance(MultiPartUpload);
instance(obj, function () {
expect(stub).to.have.been.called;
done();
});
But it doesn't work as expected. Any suggestion? Thank you :)
EDIT:
instance is the istance of the object that creates the MultiPartUpload object. The problem is that the instance signature cannot be changed and that the MultiPartUpload library is required in the file where instance is created.
In short: I have to spy the MultiPartUpload library, and the problem is that is not possible to communicate in any way with istance, where the library is used.
From the docs:
Creates a new object with the given function as the protoype and stubs
all implemented functions. The given constructor function is not
invoked
This mean that sinon.createStubInstance(MultiPartUpload); will return a new stub with all prototype functions as stubs. I think you looking for a way to spy if the MultiPartUpload function was called, so one way could be to overwrite MultiPartUpload with the stub:
var MultiPartUpload = require('knox-mpu');
var stub = sinon.stub().returns(sinon.createStubInstance(MultiPartUpload));
MultiPartUpload = stub;
instance(obj, function () {
expect(stub).to.have.been.called;
done();
});
The only way to make it work that I found was this: instead of having:
var MultiPartUpload = require('knox-mpu');
In the instance code. I changed it to:
MultiPartUpload = require('know-mpu');
Then in the test-case I simply put:
MultiPartUpload = sinon.spy();
instance(obj, function () {
expect(MultiPartUpload).to.have.been.called;
done();
});
Any way to do it better than this? (I don't like global vars). Thanks :)
Have you looked into something like https://github.com/felixge/node-sandboxed-module ? When you require the instance module, you could use SandboxedModule to substitute a spy for knox-mpu.
Edit: I can't give a complete working example, because you haven't given us all your code. But sandboxed-module works something like this:
var SandboxedModule = require('sandboxed-module')
, MultiPartUploadSpy = sinon.spy()
, expect = chai.expect
, YourInstanceModule = SandboxedModule.require('your-instance-module', {
requires: {'knox-mpu': MultiPartUploadSpy}
})
instance(obj, function () {
expect(MultiPartUploadSpy).to.have.been.called;
done();
});

Testing private members in Javascript using Sinon

I'm starting to write some javascript tests and trying to figure out what the best approach is for inspecting the private members of a module constructor. For example, in the sample below i'm using the revealing module pattern to expose the public api to my module. I want to test that privateVar is correctly set during the callback of the $.getJSON ajax request.
The second test it('should update privateVar', ...), doesn't work because myModule.privateVar is (intentionally) not in the public api for the module.
So, my question is, What is the best way to test this kind of behaviour without having to make the privateVar part of the public api? Is there a better way to factor this code for testing, or maybe a way to use something like SinonJs to spy on the private member?
define('myModule',
['jquery'],
function ($) {
var
myVar = "something",
privateVar = "something else",
doSomething = function() {
return $.getJSON('http://myapi.com/do-something', { requestData : "some data" }, function(response){
myVar = response.data.value1;
privateVar = response.data.value2;
});
};
return {
doSomething : doSomething,
myVar : myVar
};
}
);
define('test/test.myModule',
['myModule', 'chai', 'sinon', 'mocha'],
function (myModule, chai, sinon) {
describe("myModule", function() {
var expect = chai.expect;
describe('doSomething', function() {
var value1 = 'value1 value',
value2 = 'value2 value';
beforeEach(function() {
sinon.stub($, 'ajax').yieldsTo('success', {
data : { value1 : value1, value2 : value2 }
});
});
afterEach(function() {
$.ajax.restore();
});
it('should update myVar', function(done) {
myModule.doSomething();
expect(myModule.myVar).to.equal(value1);
done();
});
it('should update privateVar', function(done) {
myModule.doSomething();
expect(myModule.privateVar).to.equal(value2);
done();
});
});
});
}
);
What you are talking about here unfortunately requires an integration test, you wish to test that a variable is set as a result of an external operation, You should trust that the external method just works for your test by stubbing it out in your tests as you have done with sinon this takes care of the external call.
What you need to be able to do is to control the conditions of the test (lets say non authenticated and authenticated) then test what the result of the function is in that instance. As a rule I don't normally test private members at all but I do test desired behaviour resulting from known good and bad values..
I also read this a little while ago, which discusses private vars.
The only way you can access your private variables this way is is to add a public getter that you can later call in your test to verify the state:
In your class:
getPrivateVar : function(){ return privateVar; }
Then add to return statement:
return { getPrivateVar : getPrivateVar, };
Actually why test a private variable? It is not possible/difficult.
And what is the purpose of that variable? If it is going to be passed on as a argument to function, you can of-course test the function by spying if the function is called with the specific value, which was assigned to the private variable.
You can use rewire
let revertBack = renderCtrl.__set__(privateMember,substituteMember);
to revert back just call revertBack();
For more details see https://www.npmjs.com/package/rewire

Jasmine testing that a subclass object has been called through a Knockout subscribe

I've got a few things interacting here, and they aren't interacting well.
I have a base class:
var ObjOne = (function() {
return function() {
var self = this;
self.propertyOne = ko.observable(1);
self.observable = ko.observable(1);
self.observable.subscribe(function(newValue) {
self.propertyOne(newValue);
});
};
} ());
It has two Knockout observables, and defines a subscribe on one of them that updates the other.
I have a "subclass", extended with jQuery.extend:
var ObjTwo = (function() {
return function() {
this.base = new ObjOne();
$.extend(this, this.base);
};
} ());
And I have a Jasmine test, which is attempting to ask the question "when I update observable, is propertyOne called?"
it('Test fails to call the correct propertyOne', function() {
var obj = new ObjTwo();
spyOn(obj, 'propertyOne').andCallThrough();
obj.observable(2);
expect(obj.propertyOne).toHaveBeenCalled();
expect(obj.propertyOne()).toBe(2);
});
This fails with "Expected spy propertyOne to have been called.". When I debug, the observable is updated properly. In the actual system, it works fine (as well, even the test "is propertyOne equal to 2?" passes. When I debug into the subscribe function, self.propertyOne is not a spy, but in the test, it is.
I have a solution, but it isn't great:
it('Test calls the base propertyOne', function() {
var obj = new ObjTwo();
spyOn(obj.base, 'propertyOne').andCallThrough();
obj.observable(2);
expect(obj.base.propertyOne).toHaveBeenCalled();
expect(obj.propertyOne()).toBe(2);
});
Note the .base added to the two lines. I don't like that I've had to expose the base class, or had to touch it's properties in order to make the test run.
Here's a jsfiddle: http://jsfiddle.net/4DrrW/23/. The question is - is there a better way of doing this?
After you call $.extend(this, this.base); your object basically looks like:
{
base: {
propertyOne: ko.observable(1),
observable: ko.observable(1)
},
propertyOne: base.propertyOne,
observable: base.observable
}
When you do a spyOn for propertyOne it replaces it with a wrapper. However, the subscription is set between the actual observables and would not have any way to call the wrapper.
If you do not want to access base, then I would just remove the test that the observable was called. Checking that the value is correct seems sufficient.
Otherwise, you would probably be better off mixing in ObjOne by calling its constructor with the new object's this like:
var ObjTwo = (function() {
return function() {
ObjOne.call(this);
};
} ());
Then, the test would be fine: http://jsfiddle.net/rniemeyer/z2GU3/

Categories