Spying/testing if trigger on event handler works using Chai - javascript

I'm very new to Chai testing. I can't find good example. Basically, what I want to happen is check wether the event got triggered.
on my patientInfo.js, the code to trigger the event is
import PatientBus from 'backbone.radio';
patientAdded() {
PatientBus.trigger('patient:added');
},
then on my patientEvents.js
import PatientBus from 'backbone.radio';
this.listenTo(PatientBus, 'patient:added', this.onPatientAdded);
onPatientAdded: function onPatientAdded() {
// blah blah blah
}
Forgot to say, I'm using Marionette radio. The event handler codes above are working great. Now, I want a chai test which will check if the listener of the event received the trigger or broadcast request. I'm not sure where to start and how I will write it.

As #Sgni mentioned, you'll need to spy on your function to 1) know whether or not it was called, and 2) inspect its return value. The Sinon syntax for doing this is:
sinon.spy(yourObject, 'onPatientAdded');
To give you some intuition for how this works, the Sinon docs list this as an example:
function once(fn) {
var returnValue, called = false;
return function () {
if (!called) {
called = true;
returnValue = fn.apply(this, arguments);
}
return returnValue;
};
}
As you can see, it wraps your original function in a closure, which keeps references to your original function's return value, and whether the function was called.
Sinon-Chai just gives you nice syntax, so you can make readable assertions like the following:
expect(yourObject.onPatientAdded).to.have.been.calledOnce;
expect(yourObject.onPatientAdded).to.have.returned(something);
So yes, there is seemingly a lot of magic, but the magic comes from Sinon's use of JavaScript closures and Sinon-Chai's clever use of object properties.

Related

Why would an event listener in a JavaScript class see old context variable?

so I'm having a problem that seems to defy everything I know about how scope is handled in JavaScript with anonymous functions - but it could be something else I'm not thinking about.
I have a JavaScript object, called Element, with a constructor similar to this:
function Element(boxElement) {
var self = this;
// Set jquery instance variables
self.pageElement = null;
self.boxElement = boxElement;
... blah blah blah
// Implement triggers to empty functions
self.onElementClicked = function () {};
// Bind listeners
self._bind_listeners();
}
The bind_listeners method is defined as such
Element.prototype._bind_listeners = function() {
var self = this;
self.boxElement.on('click', function (e) {
// Don't handle if handled already
if (e.isDefaultPrevented()) return;
console.log("Got past the return");
self.onElementClicked();
});
};
And there's also a method to set the callback method onElementClicked:
Element.prototype.on_element_click = function(callback) {
var self = this;
self.onElementClicked = callback;
};
The problem I am encountering is that if I set my callback using the on_element_click method, my method doesn't see the current instance - it sees what the instance would look like just after construction.
More specifically to my situation, there's an instance variable called boxElement that refers to a JQuery element - and in Chrome's console I can see that the instance (self) still does refer to the correct element on the page, but the onElementClicked instance variable (and others) do not seem to be set from within the listener.
Feel free to revise my explanation or ask for clarification.
From the implementer perspective:
If I do this:
// Set default listener for element click
formElement.on_element_click(function () {
console.log("Hello");
});
The listener never says Hello because onElementClicked doesn't appear to be set.
However, if I instead do this:
formElement.boxElement.click(function () {
console.log("Hello");
});
It successfully says "Hello" and makes me confused.
I found the solution to my specific problem, which is a good example of how an error like this can occur. (offtopic: please feel free to add answers for other ways to produce this error - it is a very non-intuitive problem and will always be caused by an external factor)
It turns out the class I was testing with is a class that extends my Element class - BUT, it does so improperly / VERY VERY badly!
As embarrassing as it is to post this, here's the original constructor of my "subclass" (quotes for reasons soon apparent):
function StrikeoutFormElement (formElement) {
var self = this;
// Set reference to form element
self.fe = formElement;
$.extend(self, self.fe);
// Override methods
self.on_reposition(function () {
self._on_reposition();
});
}
I used JQuery's object extending function and a hacky workaround to override something. I have learned the hard way to NEVER use JQuery's extend for OOP, as it is only intended for data manipulation rather than as a language tool.
The new constructor looks like this:
function StrikeoutFormElement (elem) {
var self = this;
}
// Extend the FormElement prototype
StrikeoutFormElement.prototype = Object.create(Element.prototype);
StrikeoutFormElement.prototype.constructor = Element;
This is a method described in an MDN article somewhere. I'll post the source when I find it if someone doesn't beat me to it.
Shoutout to anyone who looked at this obscure problem and attempted to figure it out!

Jasmine Spies.and.stub method

I've been reading through the Jasmine documentation and I've been struggling to understand what the Spies .and.stub method actually does. English is not my native language, so I don't even know what the word "stub" actually means, and there is no translation for it in my language.
In the documentation it says:
When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub.
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, 'setBar').and.callThrough();
});
it("can call through and then stub in the same spec", function() {
foo.setBar(123);
expect(bar).toEqual(123);
foo.setBar.and.stub();
bar = null;
foo.setBar(123);
expect(bar).toBe(null);
});
});
What does and.stub actually do and how is it useful?
For the term, you can look at wikipedia : http://en.wikipedia.org/wiki/Test_stub
In a nutshell it's a "fake" object that you can control that replaces a "real" object in your code.
For the function, what I understand is that and.stub() removes the effect of and.callThrough() on a spy.
When you call and.callThrough, the spy acts as a proxy, calling the real function, but passing through a spy object allowing you to add tests like expectation.
When you call and.stub, or if you never call and.callThrough, the spy won't call the real function. It's really usefull when you don't want to test an object's behavior, but be sure that it was called. Helping you to keep your test truly unitary.
To complete the previous answer:
Indeed, it's not clear from the doc, but it's very clear in the source code:
https://github.com/jasmine/jasmine/blob/4be20794827a63ca5295848c58ffc478624ee4dc/src/core/SpyStrategy.js
plan = function() {};
-> the called function is empty
this.callThrough = function() {
plan = originalFn;
-> the called function is the original function
this.stub = function(fn) {
plan = function() {};
-> the called function is empty (again)

Jasmine spyOn: How to make it work when the spyed function reference is passed?

I'm using jasmine to test my javascript, and I'm finding a hard case to spec. I have this function which is being passed directly to the handler:
filters.find('#per_page').change(checkList.filter);
Now, I'd like to spy on it and see if it's being called when i change that:
describe("when I change the number of items per page", function() {
beforeEach(function()
spyOn(checkList, 'filter');
$('#per_page').val('50').trigger('change');
});
it("filters the results list", function() {
expect(checkList.filter).toHaveBeenCalled();
});
});
This test fails. Suprisingly, if I change my code above to:
filters.find('#per_page').change(function() { checkList.filter(); });
it passes. Is there a way to spy on such functions passed or do I have to resort to the anonymous function case?
Yes, there is, but to do so events have to be bound after you have added the spies on your function. The reason for this is that spies replace the property with an other function (it doesn't change the function itself). In your case, what happened is that you assigned the original function and when you added the spies you replaced the property without changing the callback that was bound.
If the way your application is made you can't add your spies before doing the events binding, than the only it can work is to use anonymous function which call the proper callback.

Testing delegate callbacks using Jasmine (JavaScript)

I have a simple JavaScript function that uses two delegates to (asynchronously) get one value back:
function getMyUserName() {
context.load(user);
context.executeQueryAsync(onGetMyUserNameSuccess, onGetMyUserNameFail);
}
function onGetMyUserNameSuccess() {
return user.get_title();
}
function onGetMyUserNameFail(sender, args) {
return args.get_message();
}
The "context" and "user" variables are already set and initialized, and the first delegate ("onGetMyUserNameSuccess") is getting the correct answer. The question is how I can test the "getMyUserName" function with Jasmine?. If I use "runs", I have no way to know the response from the delegates (also I don't see any way to know if the delegates are called). I tried to set spies to mock the delegates, but probably I didn't it correctly (I'm just starting with Jasmine).
Any help will be very welcome.
Thanks in advance,
Gustavo
In most of the cases when you have to work with async code, you should call the function by yourself. But not directly but in the way your code would call it. So in your case spy on context.executeQueryAsync and use spy.mostRecentCall.args to get the reference to the function and then call them.
var async = jasmin.spyOn(context, 'executeQueryAsync');
async.mostRecentCall.args[0]()
var args = {get_message: jasmine.createSpy()}
async.mostRecentCall.args[1]({}, args);
expect(args.get_message.toHaveBeenCalled());
Note that there is the sinon framework that have a bunch of methodes to automatically call callbacks

Unit testing jQuery document.ready function [duplicate]

This question already has answers here:
How to invoke $(document).ready(function() {}) in unit testing
(5 answers)
Calling $(document).ready(function() {...}); from another file
(1 answer)
Closed 5 months ago.
I have a question in regards to unit testing jQuery's document.ready function().
Currently I have 2 scenarios in my code:
function myFunction()
{
$(document).ready(function() { ... });
}
And:
$(document).ready(function()
{
// some really long setup code here
});
I tried to write a unit test for the first scenario, but I just couldn't get it to run into the document.ready function. As for the second scenario, I haven't come up with a way to test it yet (I'm having trouble coming up with both a way to test it and the syntax).
So assuming I cannot change the source code, are there any ways to test those functions? (assuming it is a good idea to test them)
Thanks.
You do not need to test $(document).ready as it is part of the framework and is already unit tested. When writing unit tests you need to test two things:
Your interaction with the framework. This includes things like making sure that you call the right functions with the right parameters.
Your own code - that your code does the right thing.
So what you really need to do is to make sure that whatever code that gets called from $(document).ready is correct.
function myInit(){
//...
}
function myFunction()
{
$(document).ready(myInit);
}
All you need to do now is to unit test myInit function.
What you can also do is mock out $.ready function to make sure that you are calling it:
var readyCalled = false;
$.ready = function(func){
readyCalled = (myInit == func);
}
//Your code containing `myInit` will get executed somewhere here
//....
//Then test:
test("Should have called ready", function() {
ok(readyCalled, "ready should have been called with myInit as a parameter.")
});
The function that registers the on ready handler should register another function, not an anonymous codeblock. Then you can test the code that calls $.ready() separate from the code that runs on ready. So you have:
One test to verify the right function is set as the the ready handler
Another test to verify the ready handler does the right stuff
To test scenario 1, you'll need to inject a test double for jQuery. This is difficult as if you redefine $ or jQuery, odds are you'll screw up other code that relies on it for other processing (like the test runner). At the same time your code may still want to call jQuery directly when its using utility methods like array concatenation. Any inversion-of-control pattern should address this though (http://martinfowler.com/articles/injection.html).
Anyhow, here's some code using constructor injection (using JSMock for the mocking library, and QUnit (of jQuery) for the test runner):
// the code
var createComponent = function(_$) {
var that = {};
that.OnStart = function() {
_$.ready(this.OnReady);
};
that.OnReady = function() {
};
return that;
};
// the test
test("OnStart associates the ready handler", function() {
var sut;
var mock$ = mc.createMock($);
mock$.expects().ready(isA.TypeOf(Function)).andStub(function(callback) {
equals(callback, sut.OnReady);
});
sut = createComponent(mock$);
sut.OnStart();
mc.verify();
});
test("OnReady does the right stuff", function() {
//etc
});
I use this general pattern for all event handlers in JS... You might prefer to use prototype type classes. When you pass functions as parameters to jQuery, you need to be aware that the "this" value will not be set by jQuery when those callbacks are called. In the test, this breaks because equals(callback, sut.OnReady) no longer passes. To address this, you need to make the event handlers direct members of each instance. You can imagine when there are a number of then its nice to have a util that takes a list of them, but this demonstrates making 'OnReady' a member who does not rely on 'this'.
var Component = function(_$) {
this._$ = _$;
// repeat for each event handler thats tested
this.OnReady = function() {
Component.prototype.OnReady.apply(this);
}
}
Component.prototype.Start = function() {
this._$.ready(this.OnReady);
}
Component.prototype.OnReady = function() {
}

Categories

Resources