如何测试一个函数在另一个函数之前调用

时间:2014-10-30 10:10:10

标签: javascript unit-testing jasmine

我有一些紧密耦合的遗留代码,我想用测试来覆盖。有时确保在另一个方法之前调用一个模拟方法很重要。一个简化的例子:

function PageManager(page) {
    this.page = page;
}
PageManager.prototype.openSettings = function(){
    this.page.open();
    this.page.setTitle("Settings");
};

在测试中,我可以检查open()setTitle()是否都被调用:

describe("PageManager.openSettings()", function() {
    beforeEach(function() {
        this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
        this.manager = new PageManager(this.page);
        this.manager.openSettings();
    });

    it("opens page", function() {
        expect(this.page.open).toHaveBeenCalledWith();
    });

    it("sets page title to 'Settings'", function() {
        expect(this.page.setTitle).toHaveBeenCalledWith("Settings");
    });
});

setTitle()仅在首次调用open()后才能生效。我想检查第一个page.open()是否被调用,然后是setTitle()。我想写这样的东西:

it("opens page before setting title", function() {
    expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});

但Jasmine似乎没有内置这样的功能。

我可以这样做:

beforeEach(function() {
    this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
    this.manager = new PageManager(this.page);

    // track the order of methods called
    this.calls = [];
    this.page.open.and.callFake(function() {
        this.calls.push("open");
    }.bind(this));
    this.page.setTitle.and.callFake(function() {
        this.calls.push("setTitle");
    }.bind(this));

    this.manager.openSettings();
});

it("opens page before setting title", function() {
    expect(this.calls).toEqual(["open", "setTitle"]);
});

这有效,但我想知道是否有一些更简单的方法来实现这一点。或者一些很好的方法来概括这一点,所以我不需要在其他测试中复制这段代码。

PS。当然正确的方法是重构代码以消除这种时间耦合。但是,它可能并不总是可能的,例如,与第三方库连接时。无论如何......我想首先用测试覆盖现有代码,尽可能少地修改它,然后再深入研究进一步的重构。

6 个答案:

答案 0 :(得分:5)

试试这个:

it("setTitle is invoked after open", function() {
    var orderCop = jasmine.createSpy('orderCop');
    this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
        orderCop('fisrtInvoke');
    });

    this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
        orderCop('secondInvoke');
    });

    this.manager.openSettings();

    expect(orderCop.calls.count()).toBe(2);
    expect(orderCop.calls.first().args[0]).toBe('firstInvoke');
    expect(orderCop.calls.mostRecent().args[0]).toBe('secondInvoke');
}
编辑:我刚刚意识到我的原始答案实际上与你在问题中提到的黑客一样,但是在设置间谍方面有更多的开销。使用你的" hack"它可能更简单。方式:

it("setTitle is invoked after open", function() {
    var orderCop = []
    this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
        orderCop.push('fisrtInvoke');
    });

    this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
        orderCop.push('secondInvoke');
    });

    this.manager.openSettings();

    expect(orderCop.length).toBe(2);
    expect(orderCop[0]).toBe('firstInvoke');
    expect(orderCop[1]).toBe('secondInvoke');
}

答案 1 :(得分:3)

  

我想写这样的东西:

it("opens page before setting title", function() {
    expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
     

但Jasmine似乎没有内置这样的功能。

看起来Jasmine人看过这篇文章,因为this functionality exists。我不知道它已经存在了多长时间 - 他们所有的API文档都回到了2.6提到它,尽管他们的档案旧版文档都没有提到它。

  

toHaveBeenCalledBefore(expected
  expect在另一个Spy之前调用的实际值(Spy)。

     

<强>参数:

Name        Type    Description
expected    Spy     Spy that should have been called after the actual Spy.

您示例的失败看起来像Expected spy open to have been called before spy setTitle

答案 2 :(得分:2)

为第二次调用创建一个假函数,该函数需要进行第一次调用

it("opens page before setting title", function() {

    // When page.setTitle is called, ensure that page.open has already been called
    this.page.setTitle.and.callFake(function() {
        expect(this.page.open).toHaveBeenCalled();
    })

    this.manager.openSettings();
});

答案 3 :(得分:0)

使用间谍上的.calls.first().calls.mostRecent()方法检查特定的来电。

答案 4 :(得分:0)

基本上做了同样的事情。我有信心这样做,因为我用完全同步的实现模拟了函数行为。

it 'should invoke an options pre-mixing hook before a mixin pre-mixing hook', ->
    call_sequence = []

    mix_opts = {premixing_hook: -> call_sequence.push 1}
    @mixin.premixing_hook = -> call_sequence.push 2

    spyOn(mix_opts, 'premixing_hook').and.callThrough()
    spyOn(@mixin, 'premixing_hook').and.callThrough()

    class Example
    Example.mixinto_proto @mixin, mix_opts, ['arg1', 'arg2']

    expect(mix_opts.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
    expect(@mixin.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
    expect(call_sequence).toEqual [1, 2]

答案 5 :(得分:0)

最近我开发了一个名为strict-spies的茉莉花间谍的替代品,它解决了许多其他问题:

describe("PageManager.openSettings()", function() {
    beforeEach(function() {
        this.spies = new StrictSpies();
        this.page = this.spies.createObj("MockPage", ["open", "setTitle"]);

        this.manager = new PageManager(this.page);
        this.manager.openSettings();
    });

    it("opens page and sets title to 'Settings'", function() {
        expect(this.spies).toHaveCalls([
            ["open"],
            ["setTitle", "Settings"],
        ]);
    });
});
相关问题