I'd like to check what listeners are attached to my Marionette component, for example to the controller:
Example code of the component:
var MyController = Marionette.Controller.extend({
initialize: function () {
this.listenTo(OtherModule, "start", function () {
// something happens here
});
this.listenTo(OtherModule, "stop", function () {
// something happens here
});
})
});
var myController = new MyController();
Example code of the unit test:
describe("MyController", function () {
it("should have 2 listeners registered", function () {
// ?
});
});
I can trigger the events and see if the function I wanted to use was executed with the use of the jasmine's spyOn method, but I'm curious if there's a list of attached events available directly on the component.
How can I check what is my component listening to?
I think you're approaching unit testing in the wrong way - unit tests should check that your object interacts with the outside world in the expected way. They shouldn't be concerned with implementation details (like the exact number of event listeners an Object has).
Having said that, you can use the _listeners (Backbone 1.0.x) or _listeningTo (Backbone 1.1.x) property:
var controller = new MyController;
describe("MyController", function () {
it("should have 2 listeners registered", function () {
expect(Object.keys(controller._listeners).length).toEqual(2)
});
});
Source - Marionette.Controller extends Backbone.Events, which stores listeners in that property.
I wouldn't use this approach in a unit test, but it can be very useful for debugging memory leaks.
When I want to debug this kind of thing I often use a window.MyController = MyController. Then in the console I can save window.MyController and play around with it.
It looks like it will show the objects it's listeningTo but I'm not necessarily seeing what events it's tied to in this fashion. Anyway might be a look see. I'm also using Chrome so Firebug in Mozilla might give better info.
Related
I am using Jasmine to test an Angular app and would like to test that the getItem() function within my controller is called when the ready() function of the controller is called.
--- Controller ---
var vm = this;
vm.items = [];
$ionicPlatform.ready(ready);
function ready() {
vm.items.push(getItem());
function getItem(){
var item = //do stuff to get item;
console.log('getItem called');
return item;
}
}
--- Spec ---
describe('Controller', function(){
//--- Load app with dependencies and module to test code omitted.
beforeEach(function(){
//How do I spy on getItem() to test that it was called?
//I've tried getItem = jasmine.createSpy()
//I've tried spyOn(window, 'getItem')
}
//--- Initialize the controller and a mock scope code omitted.
beforeEach(function(done){
$ionicPlatform.ready(function(){
done();
});
});
it('getItem function should get called', function(){
expect(getItem).toHaveBeenCalled();
//--- Note, getItem does not get called according to the expect statement,
//--- but does output 'getItem called' to the terminal when running the test.
});
});
Unfortunately, you've come upon a fundamental limit of Javascript unit testing with Jasmine-- you can only spyOn methods that are exposed on some object. If there is a function that is internal to another function, and not exposed in anyway, you cannot test it directly.
However, you do have two options available to you:
Expose the function in a way that it can be spied on (generally as a method of whatever Angular component you are testing).
Test it indirectly.
The first is probably relatively self-evident, but the latter may be a little confusing. Basically, you can't test directly if the getItems function is called, but the function may have downstream methods it calls or values it changes you can test. For instance, you can test that vm.items.push is larger after ready is called, or you can spyOn(console.log) and expect(console.log).toHaveBeenCalledWith('getItem called').
You can find arguments for both approaches on the internet-- I tend to prefer approach two because I don't like doing refactors solely for the purpose of testability, but many will argue that refactoring for testability generally yields better code. That choice is yours to make. Hope this helps!
Lets say I have the following Backbone view which loads two links, one with the anchor text "test1" and the other with the anchor text "test2".
I bind a click event and I get the HTML of the link that was clicked and store it inside the clickedHtml variable.
Now, this view is loaded by a Backbone router.
When the user clicks either one of the two links (test1 or test2) another view called "main" will be loaded by the router.
Now, how can I pass the "clickedHtml" variable to that view?
Should I use LocalStorage?
Should I declare it globally like window.clickedHtml?
Is there a better way?
Ty!
// file: views/test.js
define([
'jquery',
'underscore',
'backbone'
], function($, _, Backbone) {
var Test = Backbone.View.extend({
el : '.test',
initialize : function () {
var that = this;
that.$el.html('test1<br />test2');
},
events : {
'click .test a' : 'click'
},
click : function (e) {
var clickedHtml = $(e.target).html();
}
return Test;
});
Here is my router:
// file: router.js
define([
'jquery',
'underscore',
'backbone',
'views/test',
'views/main'
], function ($, _, Backbone, Test, Main) {
var Router = Backbone.Router.extend({
routes: {
'' : 'home',
'test' : 'test'
}
});
var initialize = function () {
var router = new Router();
router.on('route:home', function () {
var main = new Main();
});
router.on('route:test', function () {
var test = new Test();
});
Backbone.history.start();
}
return {
initialize : initialize
}
});
Basicly you should use Backbone.Event:(Or it's equivalents in Marionette)
//Declaration
var notificationService = {};
_.extend(notificationService, Backbone.Events);
//Used by listener
notificationService.on("alert", function(o) {
alert(o);
});
//Used by publisher
notificationService.trigger("alert", {foo:"bar"});
The real question is how does it get passed from one view to another?
The way I see it, you have 2 options:
Bubble notificationService from one view to another in initialization
Wrap the notificationService with a requirejs model that returns it (creates a 'almost global' notificationService that can be passed by requirejs).
Although I don't like singletons a bit, this case a of a singleton notificationService object that can easily get injected by requirejs in every model will come in handy.
EDIT:
Another option, the quick and dirty one, just use jquery to trigger event on the DOM (specifically the body element) and listen to body in the other view
//on Listening view, after DOM is ready
$( "body" ).on( "alert", function( event, param1, param2 ) {
alert( param1 + "\n" + param2 );
});
//on Triggering view, after DOM is ready
$( "body").trigger( "alert", [ "Custom", "Event" ] );
NOTE:
notice that once a listening view is closed, it must removes itself from listening to events (unbind/off), so you wont have memory leak
Architecturally speaking, your aim should be to keep your code generic & reusable.
One of the main things you don't want to do in a situation like this is to pass direct references from one object to another - if you end up changing the setup of one of the objects, or you need to pass data from another object as well, this can get messy really fast.
One design pattern that's widely used in situations like this is a mediator. Also known as "pub/sub" you can have a centralized standalone object that mediates information between objects. Certain objects will publish information and other objects can subscribe to them. The mediator acts as an intermediary so that the objects never have to communicate directly with each other. This makes a much more generic, reusable and maintainable solution.
More info here:
http://addyosmani.com/largescalejavascript/#mediatorpattern,
Javascript Patterns
On the Backbone side of things... If you've used Marionette, you may have come across a complimentary mini-library (also implemented by Derick Bailey) called wreqr. You can use this to create a simple mediator with low-overhead in your Backbone applications.
https://github.com/marionettejs/backbone.wreqr
It basically allows you to use backbone style events across objects. Example below:
First, you need to create a globally accessible mediator object, or add it to your app namespace or use require.js:
var mediator = new Wreqr.EventAggregator();
inside View #1
events : {
'click .test a' : 'click'
},
click : function (e) {
var clickedHtml = $(e.target).html();
// trigger an 'element:click' event, which can be listened to from other
// places in your application. pass var clickedHtml with the event
// (passed to the arguments in your eventhandler function).
mediator.trigger('element:click', clickedHtml);
}
Inside View #2
initialize: function(){
//...
this.listenTo(mediator, 'element:click', this.myEventHandler, this);
}
myEventHandler: function(elem){
// elem = clickedHtml, passed through the event aggregator
// do something with elem...
}
Backbone events are the way to go here.
When you capture the event in the view, I would bubble it up using:
click : function (e) {
var clickedHtml = $(e.target).html();
Backbone.Events.trigger("eventname",clickedHtml);
}
Then, you should be able to capture this in your router initialise function, using:
Backbone.Events.on("eventname", responseFunction); // listen out for this event
And then in the router declare a separate function:
responseFunction : function(clickedHtml)
{
//Do whatever you want here
}
I'm writing this from memory, so hopefully it make sense. I've also not tested catching an event like this i the router, but it should work.
HTH.
In the exact case you outline I would create a temp storage object on your global namespace and use that to transfer the data between your views, its a bit "hacky" but its better than using local storage, or the window object directly, at least with a temp object on your own global namespace the intent of the objects usage is known.
I find it better to use the http://backbonejs.org/#Events for a similar purpose of passing data between two views, though it does depend on how you structure your pages, if you have two views on the page representing a "control" or "component" this approach works really well.
If you post a link to your site or something I can have a look and give you some more help.
Russ
You could perhaps store it as a property on the view:
click : function (e) {
this.clickedHtml = $(e.target).html();
}
If your router can access both views, it can then simply pass the firstView.clickedHtml property to a function in the secondView (or to the initializer)
I'm using $.proxy(this, 'methodName') to use methods of my object as event handlers for DOM events.
When it comes to testing I'd like to use Jasmine's spyOn to monitor whether the callbacks get fired. However as the listener is attached within my Object's constructor by the time I get to spying on the method it's too late, and the raw, unspied function has already been used by $.proxy.
What are good approaches to tackling this? One thing I've considered is spying on the prototype's methods directly, but I'm worried about the impact this might have on each test being independent of others. Another would be to change how I attach the listeners in my code, but this seems like throwing out the baby with the bathwater.
You can spy on the prototype of object before the test starts. So it will not have any impact of your other tests.
var function A {
$.proxy(this, 'methodName');
}
a.prototype.methodName = function() {
console.log('test');
}
describe('…', function() {
var a;
before(function() {
jasmine.spyOn(a.prototype, 'methodName');
a = new A();
});
it('should…', function() {
});
});
Assuming I have something like:
var MyApp = function() {
this.name = "Stacy"
}
MyApp.prototype.updateName = function(newname) {
this.name = newname;
}
In my main page I have a :
$(function () {
var instance = new MyApp();
})
I have a button event handler that would update the name:
$("#button").on("click", function(evt) {
// Update the name in MyApp to something else...
instance.name = "john" // I do not like using instance here, because it has to be "instance" has to be created before I can use it. I want to be able to make this independent of "instance" being created or not
});
What is the proper way to do it such that the button handler would update "MyApp" to have the correct name, without explicitly using the created "instance" of myapp as part of the button's click handler?
ideally I would like to shove that jquery event handler somewhere into "MyApp" such that I could do something like:
MyApp.prototype.events = function() {
$("#button").on("click", function(evt) {
this.name = "john"
});
}
Though it doesnt work because this refers to something else.
How to properly structure my application such that the event handler is more or less updating the properties of the "MyApp" so that it can be independent of the created "instance" (i.e. i no longer have to use the "instance.")?
First, if you create an setter function, it's a good idea to use it !! :D
$("#button").on("click", function(evt) {
// Update the name in MyApp to something else...
//instance.name = "john"
instance.updateName("john");
});
And then, it does not make sense to do put an event handler inside of a method of your object MyApp, since it will never bind the onclick event until you fire events()
Then... my way to organize this, is to use the jQuery document onload to bind all the DOM objects with the function of your applications. Usually something like this:
MYAPP = {};
MYAPP.say_something = function () {
alert('lol, you clicked me!');
};
...
$(function () {
$('#my_button').click(MYAPP.say_something);
$('#other_element').mouseenter(MYAPP.another_method);
});
And for big applications, where you have to work with a lot of elements, you can organize your code much better if you have a namespace for your DOM elements, something like this:
MYAPP.fetch_dom = function () {
return {
my_button: $('#my_button'),
other_element: $('#other_element')
};
};
And you can bind the events in a very neat way
$(function () {
// first initiate DOM
my_dom = MYAPP.fetch_dom();
// Then bind events
my_dom.my_button.click(MYAPP.say_something);
my_dom.other_element.mouseenter(MYAPP.another_method);
});
This way you don't have to look for the specific elements in the DOM from a thousand points of your programme, spreading hardcoded id's everywhere and performing noneffective searches against the DOM structure.
Finally, it is much better to use literals in JS rather than using the word new. JS is a prototypical OOP language and new is a little bit "against nature" that can be a pain in the ass.
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() {
}