最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Sinon.js stub and test external function call with object as parameter later modified by ref - Stack Overflow

programmeradmin0浏览0评论

I'm looking to test the call my function makes to another function, particularly an argument that is an object.

The issue is that sinon.js seems to store a reference to the object parameter given in its arguments array, which is naturally expected. However, this creates a problem when the parameter is later modified by ref, which changes the apparent value of those parameters at the time the function was called.

What's the best way to unit test a situation like this?

Here is a contrived example:

/

var view = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this);    
    },

    dostuff: function () {
        var coords = { x: 0, y: 0 };
        for (var i = 0; i < 10; i++) {
            this.otherstuff(coords);

            coords.x += 10;
        }
    },

    otherstuff: function (coord) {
        // Stubbed
    }
});

test("a test", function() {
    // Arrange
    var blah = new view();

    sinon.stub(blah, 'otherstuff');

    // Act
    blah.dostuff();

    // Assert
    var expectedFirstCallCoords = { x: 0, y: 0 };

    // All assertions on the value of x for a particular call would seem to be impossible.
    // blah.otherstuff.firstCall.args[0].x --> 100
    deepEqual(blah.otherstuff.firstCall.args[0], expectedFirstCallCoords, 'parameter changed by ref, untestable?');
});​

I can think of various hacks around this 'issue'. Is there a cleaner approach that doesn't involve cloning the object or modifying my production code simply for the sake of getting sinon to work?

I'm looking to test the call my function makes to another function, particularly an argument that is an object.

The issue is that sinon.js seems to store a reference to the object parameter given in its arguments array, which is naturally expected. However, this creates a problem when the parameter is later modified by ref, which changes the apparent value of those parameters at the time the function was called.

What's the best way to unit test a situation like this?

Here is a contrived example:

http://jsfiddle/xtfQu/

var view = Backbone.View.extend({
    initialize: function () {
        _.bindAll(this);    
    },

    dostuff: function () {
        var coords = { x: 0, y: 0 };
        for (var i = 0; i < 10; i++) {
            this.otherstuff(coords);

            coords.x += 10;
        }
    },

    otherstuff: function (coord) {
        // Stubbed
    }
});

test("a test", function() {
    // Arrange
    var blah = new view();

    sinon.stub(blah, 'otherstuff');

    // Act
    blah.dostuff();

    // Assert
    var expectedFirstCallCoords = { x: 0, y: 0 };

    // All assertions on the value of x for a particular call would seem to be impossible.
    // blah.otherstuff.firstCall.args[0].x --> 100
    deepEqual(blah.otherstuff.firstCall.args[0], expectedFirstCallCoords, 'parameter changed by ref, untestable?');
});​

I can think of various hacks around this 'issue'. Is there a cleaner approach that doesn't involve cloning the object or modifying my production code simply for the sake of getting sinon to work?

Share Improve this question edited Oct 15, 2012 at 19:48 Adam Terlson asked Oct 15, 2012 at 19:28 Adam TerlsonAdam Terlson 12.7k4 gold badges46 silver badges63 bronze badges 6
  • This looks like a flaw in Sinon. It probably should clone the arguments, at least if they are native objects. Have you asked in their mailing list? Christian might either have a work-around, or be able to patch it quickly. Or he might well be able to suggest an alternate technique. I can't see anything that isn't fairly intrusive. – Scott Sauyet Commented Oct 15, 2012 at 19:48
  • @ScottSauyet Thanks for the confirmation that I'm not crazy. :) The only issue with sinon cloning those objects is that tests with strictEquals will fail against them. If I asserted on instead: strictEquals(blah.otherstuff.firstCall.args[0], expectedFirstCallCoords) that would fail against a clone. In my contrived example, that's no issue but what if expectedFirstCallCoords were injected into the function/class? That'd be very undesired. Best case, that I can see anyway, is that it'd need to store original object references and cloned objects both, and that would be confusing. – Adam Terlson Commented Oct 15, 2012 at 19:51
  • Yes, I didn't consider strictEquals. (Do you use that in testing? I never do.) I don't really see a great solution for this. But as to confirming that you're not crazy, I'm going to leave that between you and your shrink! :-) Best of luck! – Scott Sauyet Commented Oct 15, 2012 at 20:15
  • @ScottSauyet I, personally, consider use of equal to be of bad practice. Consider when you're testing your values that equal(true, 1); passes. – Adam Terlson Commented Oct 15, 2012 at 20:22
  • I mostly use JSTestDriver, and the assertion framework's assertEquals is a deep equals pare, but not a reference parison. I don't use assertSame, or not often, which is a reference parison. But I also have, e.g., assertTrue, assertNotNull, to catch more specific types. On the other hand, I often want any truthy value to pass my boolean tests, so we may have very different standards! :-) – Scott Sauyet Commented Oct 15, 2012 at 20:31
 |  Show 1 more ment

1 Answer 1

Reset to default 7

Credit to Mantoni on the related Github discussion on this:

var param = { property: 'value' };
var engage = sinon.stub();
var engageWithValue = engage.withArgs(sinon.match({ property : 'value' }));

engage(param);

param.property = 'new value';

sinon.assert.calledOnce(engageWithValue); // passes

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论