在为baz.View.likeButton
编写测试时,我遇到的问题是,当我测试是否在我触发的事件上调用了存根时,永远不会调用此存根。当我直接调用this.view._update
并且期望调用我的存根likeStub
或unLikeStub
时,也会发生这种情况。我已经检查过元素退出,但不明白为什么我的期望失败了。
我错过了什么?
我的按钮视图继承自BaseButton
,后者继承自base
。
baz.View.likeButton = baz.View.BaseButton.extend({
template: _.template($('#like-button').html()),
sPaper: null,
sPolyFill: null,
sPolyEmpty: null,
liked: null,
likeButn: null,
model: null,
events: {
"click button.icon": "toggleLike"
},
initialize: function (options) {
// inherit from BaseButton
View.BaseButton.prototype.initialize.apply(this, [options]); // inherit from BaseButtonView
this.model = options.model;
this.liked = this.model.get('liked');
// set initial variables
this.likeButn = this.$("button.icon", this);
// pass in el on instantiation
this.el = options.el;
this.svgNode = this.likeButn.find("svg").get(0); // find the svg in the likeButn and get its first object
this.sPaper = Snap(this.svgNode); // pass the svg object into Snap.js
this.sPolyFill = this.sPaper.select('.symbol-solid');
this.sPolyEmpty = this.sPaper.select('.symbol-empty');
this.likeButn.addClass(this.liked ? "liked": "unliked");
this._update();
if(this.model)
{
this.listenTo(this.model, "change:liked", _.bind(this._modelChange, this))
}
},
render: function () {
this.$el.html(this.template());
this._update();
return this;
},
toggleLike: function () {
this.liked = !this.liked;
this._update();
},
_modelChange: function() {
var bNewState = this.model.get('liked');
if(bNewState !== this.liked)
{
this.liked = bNewState;
this._update();
}
},
_update: function () {
if ( this.liked === true ) { // if liked is false, remove class, add class and set isLiked to true, then animate svg to liked position
this._like();
} else if ( this.liked === false ) { // is liked is false, remove class, add class, set isLiked to false, then animate svg to unliked position
this._unlike();
}
},
_like: function() {
this.likeButn.removeClass("unliked");
this.likeButn.addClass("liked");
this.animateLike();
if(this.model.get('liked') !== true)
{
this.model.set('liked', true);
this.model.set("numLikes", this.model.get("numLikes") + 1);
// wrapper for API
likeEntity("/items/"+this.model.id); // should do something better than this to send like to server
}
},
_unlike: function() {
this.likeButn.removeClass("liked");
this.likeButn.addClass("unliked");
this.animateUnlike();
if(this.model.get('liked') !== false)
{
this.model.set('liked', false);
this.model.set("numLikes", this.model.get("numLikes") - 1);
// wrapper for API
unlikeEntity("/items/"+this.model.id); // should do something better than this to send like to server
}
},
animateLike: function () {
this.sPolyFill.animate({ transform: 't9,0' }, 300, mina.easeinout);
this.sPolyEmpty.animate({ transform: 't-9,0' }, 300, mina.easeinout);
},
animateUnlike: function () {
this.sPolyFill.animate({ transform: 't0,0'}, 300, mina.easeinout);
this.sPolyEmpty.animate({ transform: 't0,0' }, 300, mina.easeinout);
}
});
baz.View.base = Backbone.View.extend({
// initialize variables and render template
initialize: function (options) {
this.render(); // render template and initializes above values
},
render: function () {
return this; // A good convention is to return this at the end of render to enable chained calls.
}
});
baz.View.BaseButton = baz.View.base.extend({
// backbone event hash - sets functions to events on selected buttons
events: {
"click button": "triggered"
},
// initialize variables and render template
initialize: function (options) {
baz.View.base.prototype.initialize.apply(this, [options]); // inherit from base
this.btn = this.$("button.normal-buttons");
},
// fn to be called for normal and cancel buttons
triggered: function (event) {
this.trigger('click', this);
this.btn.blur();
}
});
我的测试代码:
describe("Like Button View", function () {
before(function () {
this.$fixture = $('<div id="likeButton-view-fixture"></div>');
});
after(function () {
$("#fixtures").empty();
});
describe("Like button when attribute 'LIKED' is FALSE", function () {
beforeEach(function () {
// set-up fake server
this.server = sinon.fakeServer.create();
this.server.autoRespond = true;
// empty out and rebind the fixture for each run
this.$fixture.empty().appendTo($("#fixtures"));
this.model = new baz.Model.item({
liked: false,
numLikes: 0
});
this.view = new baz.View.likeButton ({
el: this.$fixture,
model: this.model
});
this.renderSpy = sinon.spy(this.view, "render");
this.updateStub = sinon.stub(this.view, "_update");
this.toggleLikeStub = sinon.stub(this.view, "toggleLike");
this.modelChangeStub = sinon.stub(this.view, "_modelChange");
this.likeStub = sinon.stub(this.view, "_like");
this.unlikeStub = sinon.stub(this.view, "_unlike");
this.animateLikeStub = sinon.stub(this.view, "animateLike");
this.animateUnlikeStub = sinon.stub(this.view, "animateUnlike");
this.snapAnimSpy = sinon.spy(Snap, "animate");
});
afterEach(function () {
this.view.model.destroy();
this.renderSpy.restore();
this.updateStub.restore();
this.toggleLikeStub.restore();
this.modelChangeStub.restore();
this.likeStub.restore();
this.unlikeStub.restore();
this.animateLikeStub.restore();
this.animateUnlikeStub.restore();
this.snapAnimSpy.restore();
// undo our server
this.server.restore();
});
it("call render and view should exist", function () {
var _view = this.view.render();
expect(_view).to.equal(this.view);
expect(this.renderSpy).to.have.been.called;
expect(this.view).to.be.ok;
});
it("should have class 'unliked'", function () {
var _element = $(".icon")[0];
expect(_element.className).to.include("unliked");
});
it("trigger 'toggleLike' when button is clicked should change liked value and call update", function () {
var _event = $.Event("click");
var _element = $(".icon");
_element.trigger(_event);
expect(this.toggleLikeStub).to.have.been.called; // this fails
expect(this.view.model.get("liked")).to.deep.equal(true); // this fails
expect(this.updateStub).to.have.been.called; // this fails
});
it("should like an item", function () {
var _element = $(".icon")[0];
this.view._like();
expect(_element.className).to.include("liked");
});
});
});
答案 0 :(得分:1)
好的,所以这里有多个问题。
this.view._update
当我直接拨打
this.view._update
并且期望我的存根likeStub
或unLikeStub
也被调用时,也会发生这种情况。
在此之后你是这样做的吗?:
this.updateStub = sinon.stub(this.view, "_update");
toggleLikeStub
我认为这是失败的,因为在调用toggleLike
之前,原始sinon.stub()
被指定为事件处理程序。尝试做这样的事情:
this.updateStub = sinon.stub(baz.View.likeButton.prototype, "_update");
this.view = new baz.View.likeButton ({
el: this.$fixture,
model: this.model
});
你在哪里:
expect(this.updateStub).to.have.been.called; // this fails
由于updateStub()
被调用, toggleLikeStub()
不会被调用。来自documentation for sinon.stub()
:
使用存根包装现有函数时,不会调用原始函数。
你确定你不想要间谍吗?
其它
我只是想确保你知道而不是这样做:
this.toggleLikeStub = sinon.stub(this.view, "toggleLike");
// ..
this.toggleLikeStub.restore();
你可以这样做:
sinon.stub(this.view, "toggleLike");
// ..
this.view.toggleLike.restore();
更好的是你可以做类似的事情:
var meths = [
{sinon_meth: 'stub', view_meths: ['_update', 'toggleLike', ...]},
{sinon_meth: 'spy', view_meths: ['render', 'animate', ...]},
];
meths.forEach(function (group) {
group.view_meths.forEach(function (view_meth) {
sinon[group.sinon_meth](view, view_meth);
});
});
// ...
meths.forEach(function (group) {
group.view_meths.forEach(function (view_meth) {
view[view_meth].restore();
});
});
在这种情况下,如果您进行相同的设置并在每次测试前将this.view
吹走,我甚至不确定您是否需要恢复它们。