I am writing a jasmine spec against my backbone app. However, I got stuck with this problem. Not sure why my spy function doesn't get invoked. I want to make sure when the model is changed, it should call #render.
Here is my backbone view:
class App.Views.Main extends Backbone.View
initialize: () ->
#model.on("change", #render, this)
render: () ->
console.log('rendering')
return
Here is my jasmine spec:
it "should render when change is triggered", ->
renderSpy = sinon.spy(#view, 'render')
#view.model.trigger('change')
expect(renderSpy.called).toBeTruthy()
Another thing that confuses me is that when this spec runs, it actually invokes the original method. The console log is always displayed. Anyone could help me?
Updated:
As answered below by Leonardo, I make changes with the following changes:
it "should render when reset is triggered", ->
renderSpy = sinon.spy(App.Views.Main.prototype, 'render')
#view.model.trigger('change')
expect(#renderSpy.called).toBeTruthy()
renderSpy.restore()
It works, but the problem is that it invokes the original method. I just wonder why?
I think this is the same that is happenning here:
https://stackoverflow.com/a/9012788/603175
Basically, you need to create the spy before you execute the constructor that executes the event listening, which binds the function to 'this' context.
Related
I am new to jasmine testing in react and this is driving me crazy.
I am trying to test a simple button click below. But the spy isn't working.
it('fires the clearTags handler', function() {
var handler = jasmine.createSpy();
var element = ReactTest.renderIntoDocument(
React.createElement(SomeComponent));
spyOn(element,'clearTags')
var clearTagsLink = ReactTest.findRenderedDOMComponentWithClass(element, 'clear-tags-link');
ReactTest.Simulate.click(clearTagsLink);
expect(handler).toHaveBeenCalled();
});
I am getting the error Expected spy unknown to have been called. Any ideas? I have tried using .and.callThrough() but that didn't work either.
Spy's are typically placed on method calls. Any sort of method call within the method being spied on, will not be executed by Jasmine unless you attach and.callThrough like you have done. However you're attaching a spy to a variable and not a method within the React library, which it appears you want to do.
If I'm correct, I would think you would want to setup your spy like so;
spyOn(React, 'createElement');
expect(React.createElement).toHaveBeenCalled();
For a more robust test you would probably want to use the jasmine toHaveBeenCalledWith("passing in parameters") you expected it to be called with.
I'm trying to test a view in Backbone Marionette but the onShow() never gets called so I can't test a method that is being called in that method.
views/test.coffee
onShow: () ->
debugger # this never happens when I run the Jasmine tests
alert "HI"
spec/javascripts/views/test_spec.coffee
describe 'a test', ->
beforeEach ->
#view = new window.TestView
#view.render()
it "does something", ->
# not important
onShow() generally only gets called when you show it inside a region. There a two options you have for testing.
1) call onShow manually after render:
#view.render();
#view.onShow();
2) show the view inside a region:
You can just make a new region inside your test file, just add a detached DOM element if you don't need to use the DOM, otherwise you can just make an element and put it in the DOM.
Sidenote*** I don't know CoffeeScript, so the following might not be syntactically correct!
beforeEach ->
#view = new window.TestView
#testRegion = new Backbone.Marionette.Region({el: document.createElement('div')})
#testRegion.show(#view)
You can also trigger onShow. Sorry no CoffeeScript.
view.triggerMethod("show");
I am trying to create a custom event for my model but apparently the custom event get triggered no matter what unless I use "anonymous" function definition as a callback
Here is the pseudo code of my app structure
//Router
initialize: ->
this.user = new User()
this.view = new View({model:this.user})
this.view.render()
//View
initialize: ->
//This event binding get triggered no matter what
//this.model.on("custom:event", this.triggerMe(), this)
//This works properly. Only triggered when I call model.trigger("custom:event")
this.model.on("custom:event", function(){console.log("I WORK!!");}))
triggerMe: ->
//I GET TRIGGER NO MATTER WHAT
you are invoking a function here:
this.triggerMe()
it should be this.triggerMe
this.model.on("custom:event", this.triggerMe, this)
Adding () or .call() or .apply() is invoking a function not a reference to it.
By passing this.triggerMe() you automatically execute the triggerMe function (because you add parentheses, and by so invocating it).
What you need to do, is to pass a reference to the function. Like so:
this.model.on("custom:event", this.triggerMe, this)
Having seen it in a couple of tutorials, I'm trying to execute the following line of code in my view
#model.on('change', #render, this)
Unfortunately the change event is not firing and therefore my view is not re-rendering.
I've tried binding to different events and creating a couple of custom events using the trigger function but nothing seems to be firing at all on the model. Furthermore, there are no errors coming from the console. The change event is working fine on a different collection. I'm using Zepto 1.0, Backbone.js 0.9.2 and Underscore.js 1.3.1
EDIT: I'm trying to execute the following from the Router
place: (id) ->
#model = new GM.Models.Place({id: "#{id}"})
#model.fetch
view = new GM.Views.Place(model: #model)
$('#container').html(view.render().el)
And my model is set up like this:
class GM.Models.Place extends Backbone.Model
urlRoot: '/mobile/place'
I am wondering if anyone has experienced similar problems before and has a quick fix.
If not and you need more of the code to find an explanation please let me know...
You're not actually calling the #model.fetch method anywhere. This:
#model.fetch
is not a method call, you need to add parentheses or arguments if you want to call the method:
#model.fetch()
# or
#model.fetch success: -> ...
# etc.
Otherwise you're just producing this.model.fetch; in the JavaScript and that doesn't do anything useful.
I'm trying to implement view tests for a Coffeescript implementation of the ubiquitous backbone.js 'todo' example (see github.com/rsim/backbone_coffeescript_demo.)
My jasmine tests of the above demo work pretty well, except for view events. I expect I am stuck on one or both of the following i) I do not understand the event binding in the view code, ii) I do not understand how to properly set up the Jasmine test of the view code events.
Here is an example of the 'edit' event...
class TodoApp.TodoView extends Backbone.View
tagName: "li"
template: TodoApp.template '#item-template'
events:
"dblclick div.todo-content" : "edit"
...
initialize: ->
_.bindAll this, 'render', 'close'
#model.bind 'change', #render
#model.bind 'destroy', => #remove()
render: ->
$(#el).html #template #model.toJSON()
#setContent()
this
edit: ->
$(#el).addClass "editing"
#input.focus()
...
...now here's a test of whether focus was gained upon double clicking:
describe "edit state", ->
li = null
beforeEach ->
setFixtures('<ul id="todo-list"></ul>')
model = new Backbone.Model id: 1, content: todoValue, done: false
view = new TodoApp.TodoView model: model, template: readFixtures("_item_template.html")
$("ul#todo-list").append(view.render().el)
li = $('ul#todo-list li:first')
target = li.find('div.todo-content')
expect(target).toExist()
target.trigger('dblclick') # here's the event!
it "input takes focus", ->
expect(li.find('.todo-input').is(':focus')).toBe(true)
The expectation on neither i) the spy nor ii) the focus is met.
Is there a peculiarity to testing backbone.js event code about which I should be aware in Jasmine?
you're spying on the view's edit method. this replaces the method with a spy object, which means the actual edit method won't get called. therefore, you're #input.focus will never fire.
since you want the test to actually call your edit method, i would remove the spy for it.
side note: don't call expect methods in your beforeEach. if you truly need to set an expectation on those, create an it block for them.
I'm not great with coffescript so I might be missing something but where are you setting up your spy?
In order to test event calling you may need to refresh the view's events once you've set up the spy.
spyOn(view, 'edit');
view.delegateEvents();
target.trigger('dblclick');
it("should call edit when target is double clicked", function() {
expect(view.edit).toHaveBeenCalled()
});
The issue with this is that Backbone.View events object is using event delegation. For the events to be able to be called work the element has to be part of DOM, you can do this by doing something like $('body').append(someView.el) in your beforeEach. Personally, I try not to test if Backbone is correctly setting the events and triggering clicks manually, is more practical for unit tests to call the callback handlers directly avoiding the DOM completely which can slow down your tests a lot.
For :focus is the same problem, there has to be an element in the DOM so that jQuery can tell if an element is focused. In this case it's better to set some state as part of your component and not checking for state via querying the DOM, e.g.: someView.hasFocus === true. Alternatively you can spy on the elements focus implementation and check if it was called.
I did not write my test in coffeescript, but I did have the same problem, so I hope you will forgive me for answering in javadcript. I ended up breaking down your test into two different tests. First, I tested if calling the view's edit function set the focus on the input box. After that, I tested whether the edit was called when the label was double-clicked, and have not yet gotten that test to pass. But here's how I tested if the edit function worked.
describe ("A todo item view", function() {
var my_model;
var todo_view;
beforeEach(function() {
my_model = new Todo({content:"todo value", done:false});
todo_view = new TodoView({model:my_model});
});
it("should set the focus on the input box when the edit function is called", function(){
$('body').append( todo_view.$el ); //append the view to Specrunner.html
todo_view.edit(); //call the view's edit function
var focus= document.activeElement; //finds what element on the page has focus
expect(focus).toBe('.todo-input'); //jasmine-jquery matcher checks if focused element has the .todo-input class
});
Something that might be causing problems is that your model And your view are declared inside beforeEach. Declaring them inside beforeEach means they only exist inside beforeEach's scope, and no longer exist when you run your it.
Also, does setFixtures do what you think it does? The focus cannot be set on an element that is not part of the DOM tree, so I appended the view's el to the body of the jasmine spec itself. (I'm using the HTML specrunner, not the command-line version) That makes it part of the dom tree and therefore allows it to have focus, and also makes whether it has focus testable.