- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在为 baz.View.likeButton
编写测试时,我遇到了这样的问题:当我测试是否已在我触发的事件上调用 stub 时,该 stub 永远不会被调用。例如,当我直接调用 this.view._update
并期望我的 stub likeStub
或 unLikeStub
也被调用时,也会发生这种情况。我已经检查过该元素是否存在,但不明白为什么我的期望失败了。我错过了什么?
我的点赞按钮 View 继承自BaseButton
,而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");
});
});
});
最佳答案
好的,所以您这里有多个问题。
this.view._update
This is happening too when I directly call
this.view._update
for example and expect my stubslikeStub
orunLikeStub
to be called too.
这之后你还会这样做吗?:
this.updateStub = sinon.stub(this.view, "_update");
toggleLikeStub
我认为这是失败的,因为在调用 sinon.stub()
之前,原始的 toggleLike
已被指定为事件处理程序。尝试做这样的事情:
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()
:
When wrapping an existing function with a stub, the original function is not called.
你确定不想要 spy 吗?
杂项
我只是想确保您知道,而不是这样做:
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
,我不确定是否需要恢复它们。
关于javascript - 诗农 stub 未被调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29536108/
一些背景: 我正在从事的项目使用 python-ldap图书馆。由于我们是一个混合操作系统开发团队(一些使用 Linux,一些使用 macOS 和一些 Windows),我试图让项目在所有环境中构建。
我是一名优秀的程序员,十分优秀!