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
Related
working on a script and I thought dot notation would be a good way of building methods to use later on in the grander scheme of the script.
the original system would declare functions written as
memRead();
memReadGlobal();
memWrite();
memEtc();.......
but I wanted to change this to
mem.Read();
mem.Read.Global();
Here is an example
var mem = {
Read: {
function() {
console.log('Hello World')
},
Global:
function(key) {
console.log('Goodbye World')
},
},
}
I can call mem.Global just fine, but I can't call mem.Read
I can declare mem.Read if I add another object like Local(mem.Read.Local), but I feel like writing local is redundant and would like to avoid that.
Is there a way to create a nested function like I describe above?
You can do that, but not with an object initializer expression.
var mem = {
Read() {
console.log("Hello from Read");
}
};
mem.Read.Global = function() {
console.log("Hello from Global");
};
mem.Read();
mem.Read.Global();
I use angularJS(1.4) for frontend only.
I have passed the JS-class DummyClass to an angularJS-Service called TLSService, and I added this service to an angularJS-Controller named mLxController.
I'm having problems accessing variables and methods of the DummyClass from the mLxController.
For example, as you will see in the code below, I can't retrieve a class variable String.
I use window.alert(String) to check that.
Instead of the String from the DummyClass, 'undefined' is displayed in the window.
I think it's worth mentioning, that when adding the window.alert("DummyClass calls.") in the constructor of the DummyClass, the alert will immedialtely be shown after loading the corresponding URL.
That's the code of the mLxController.js :
angular.module('mApp')
.controller('mLxController', function('TLSService', $scope, $state, $stateParams){
...
//this function is called in `index.html`
$scope.callTLSService = function(){
window.alert(TLSService.response);
}
...
});
Here's the code for dummyClass.js :
class DummyClass {
constructor() {
this.response = "Hi Controller! Service told me you were looking for me.";
}
}
Here's tlsService.js :
angular.module('mApp').service('TestClaServScript', function(){new DummyClass()});
UPDATE:
I have managed to make the DummyClass usable to the mLxController.
Although I'm pretty sure that my solution is not recommendable practice.
Basically, I moved the DummyClass into the same file as the TLSService.
Also, DummyClass and it's path isn't mentioned in the main index.html, anymore.
Accordingly, tlsService.js looks like this, now:
angular.module('mApp').service('TestClaServScript', function(){
this.clConnect = function(inStr){
var mDummy = new DummyClass(inStr);
return mDummy;
}
});
class DummyClass {
constructor(inStr){
this.inStr = inStr;
this.response =
"DummyClass says: \"Hi Controller! Service told me you were looking for me.\"";
this.charCount = function(inStr){
var nResult = inStr.length;
var stRes = "btw, your String has "
+(nResult-1)+", "+nResult+", or "+(nResult+1)+" characters.\nIDK."
return stRes;
}
}
}
and mLxController.js:
angular.module('mApp')
.controller('mLxController', function('TLSService',$scope,$state, $stateParams){
...
$scope.makeDummyCount = function(){
var mDummy = TestClaServScript.clConnect("This string is for counting");
window.alert(mDummy.charCount(mDummy.inStr));
}
...
});
There must be a way to properly import DummyClass, so that I can keep separate files.
I will do some more research and I will keep trying.
UPDATE 2: Problem solved
The provided answer to my question helped me implementing TLSService in the originally planned way.
I'd like to post the final version of the code here, in hope that it will help some beginner, like I am.
tlsService.js:
angular.module('mApp').service('TLSService', function(){
this.mCwParam = function(inputStr){
return new DummyClass(inputStr);
}
});
DummyClass stays the same like I posted it in the first Update, but it has its own file dummyClass.js, again.
mLxController.js:
angular.module('mApp')
.controller('mLxController', function('TLSService', $scope, $state, $stateParams){
...
//this function is called in the mLx-view's `index.html`
$scope.askDummyCount = function(){
var mService = TLSService.mCwParam("String, string, string, and all the devs that sing.");
window.alert(mService.charCount());
}
...
});
Also, TLSService and DummyClass ar added in the apps main index.html.
A problem in your original setup is when you register your class as a service, you're not returning the instance of the class:
function(){new DummyClass()}
Should be:
function(){return new DummyClass()}
Autoreturning only works when you don't use curly braces, like
() => new DummyClass()
I'm testing code that instantiates an object from an external library. In order to make this testable, I've decided to inject the dependency:
Boiled down to:
const decorator = function (obj, _extLib) {
var ExtLib = _extLib || require('extlib')
config = determineConfig(obj) //This is the part that needs testing.
var el = new ExtLib(obj.name, config)
return {
status: el.pay({ amt: "one million", to: "minime" })
bar: obj.bar
}
}
In my test, I need to determine that the external library is instantiated with the proper config. I'm not interested in whether this external library works (it does) nor wether calling it, gives results. For the sake of the example, let's assume that on instantiating, it calls a slow bank API and then locks up millions of dollars: we want it stubbed, mocked and spied upon.
In my test:
it('instantiates extLib with proper bank_acct', (done) => {
class FakeExtLib {
constructor(config) {
this.acct = config.bank_acct
}
this.payMillions = function() { return }
}
var spy = sandbox.spy(FakeExtLib)
decorator({}, spy) // or, maybe decorator({}, FakeExtLib)?
sinon.assert.calledWithNew(spy, { bank_acct: "1337" })
done()
})
Do note that testing wether e.g. el.pay() was called, works fine, using spies, in sinon. It is the instantiation with new, that seems untestable.
To investigate, let's make it simpler even, testing everything inline, avoiding the subject under test, the decorator function entirely:
it('instantiates inline ExtLib with proper bank_acct', (done) => {
class ExtLib {
constructor(config) {
this.acct = config.bank_acct
}
}
var spy = sandbox.spy(ExtLib)
el = new ExtLib({ bank_acct: "1337" })
expect(el.acct).to.equal("1337")
sinon.assert.calledWithNew(spy, { bank_acct: "1337" })
done()
})
The expect part passes. So apparently it is all called properly. But the sinon.assert fails. Still. Why?
How can I check that a class constructor is called with proper attributes in Sinon?" Is calledWithNew to be used this way? Should I spy on another function such as the ExtLib.prototype.constructor instead? If so, how?
You're really close.
In the case of your simplest example, you just need to create el using the spy instead of ExtLib:
it('instantiates inline ExtLib with proper bank_acct', (done) => {
class ExtLib {
constructor(config) {
this.acct = config.bank_acct
}
}
var spy = sandbox.spy(ExtLib)
var el = new spy({ bank_acct: "1337" }) // use the spy as the constructor
expect(el.acct).to.equal("1337") // SUCCESS
sinon.assert.calledWithNew(spy) // SUCCESS
sinon.assert.calledWithExactly(spy, { bank_acct: "1337" }) // SUCCESS
done()
})
(Note that I modified the test to use calledWithExactly to check the arguments since calledWithNew doesn't seem to check the arguments properly in v7.2.2)
I m actually using a micro framework created by my society in which we use Mongoose.
To manage the mongoose object, we created a modelfactory, that returns us a model corresponding to the mongoose name object.
Actually, I m working on an authentication service in which I inject this modelfactory.
I need to unit test it with mocha and sinonjs, but I m a bit lost...
This is my authentication Service method that I want to test :
class AuthenticationService extends Service
constructor: (modelFactory)->
super(modelFactory)
#authorizedClientIds = [
"123456"
"toto"
]
#OAuthAccessTokensModel = #modelFactory.getSchema('OAuthAccessTokens')
#OAuthClientsModel = #modelFactory.getSchema('OAuthClients')
#OAuthUsersModel = #modelFactory.getSchema('OAuthUsers')
#OAuthRefreshTokensModel = #modelFactory.getSchema('OAuthRefreshTokens')
## Get an access token from the bearer token ##
getAccessToken: (bearerToken, callback)->
#OAuthAccessTokensModel.findOne({accessToken: bearerToken}, callback)
module.exports = AuthenticationService
I want to test the getAccessToken method, but I have clearly no idea how to make it work...
I've tried to make something like :
describe("Authentication Service", function () {
var service;
before(function () {
ModelFactory = use('/app/core/config/database/ModelFactory');
var mock = sinon.mock(ModelFactory.getFactoryInstance([]));
mock.expects("getSchema").withArgs("user").return({name:'user',getName:function(){}});
service = new AuthenticationService(mock);
});
describe("getAccessToken", function () {
it('should return-1 when the value is not present', function () {
var proxy = once(service.getAccessToken());
mock.verify();
});
});
});
How should I do to test it correctly ?
EDIT :
I've tried something, but it seems weird to test because I propose the result to compare, but the result expected too.. So I could never fail the test :x...
describe("Authentication Service", function () {
var service;
before(function () {
ModelFactory = use('/app/core/config/database/ModelFactory');
var factory = new ModelFactory([]);
sinon.stub(factory, "getSchema").returns({findOne: sinon.stub().returns()});
service = new AuthenticationService(factory);
});
describe("getAccessToken", function () {
it('Check if the access token correspond to a database entry', function () {
stubResult = {token: '123456'};
service.getAccessToken = sinon.stub().withArgs('1234').returns(undefined);
assert.equal(service.getAccessToken(), undefined);
});
});
});
Some help ?
Thanks for advance
The Unit test should test something that is not mocked/stubbed.
When you have a difficult method handleUnknownToken() this function can call your Authentication Service. The assert() should verify that the handling of the 'undefined' works as expected.
In other words: When you want to unit test f(x) = g()+h() +j(); you can test the correct implementation of g() by stubbing h() and j(), test h() by stubbing g() and j() and test j() by stubbing g() and h().
EDIT: The explanation above is abstract, since I do not know Mongoose/Mocha/Sinonjs. Beneath I'll try to focus on the case described.
When your service getAccessToken() is completely stubbed, the next tests
will succeed when your stub definition is correct:
testUnknown() {
constant UKNOWN_ITEM='1234';
assert.equal(service.getAccessToken(UNKNOWN_ITEM), undefined);
}
testTimeout() {
constant DIFFICULT_ITEM='1235';
assert.equal(service.getAccessToken(DIFFICULT_ITEM), STATUS_TIMEOUT);
}
testRevoked() {
constant REVOKED_ITEM='1236';
assert.equal(service.getAccessToken(REVOKED_ITEM), STATUS_DENIED);
}
testActive() {
constant ACTIVE_ITEM='1237';
assert.equal(service.getAccessToken(ACTIVE_ITEM), STATUS_OK);
}
Your test must include some logic you don't stub.
Waht is the code around calling getAccessToken()? Something like a function
isTokenOK(), that will look at the status and retry 5 times after a timeout?
With the stubs implemented for the above test you can test the boolean function isTokenOK() with
testUnknown() {
assertFalse(isTokenOK(UNKNOWN_ITEM));
}
testTimeout() {
assertFalse(isTokenOK(DIFFICULT_ITEM));
}
testRevoked() {
assertFalse(isTokenOK(REVOKED_ITEM));
}
testActive() {
assertTrue(isTokenOK(ACTIVE_ITEM));
}
And now, when somebody changes the implementation of isTokenOK(), your unit test can fail. When the unit test is failing, you must look who is right.
Maybe a token that has been revoked can be used for authentication the first 10 minutes after revocation and isTokenOK(REVOKED_ITEM) should be true.
Oh well, than you must add a new test for REVOKED_ITEM_YESTERDAY.
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/