JS学习之JavaScript异步方法的单元测试

背景说明

最近因工作上的原因,研究了一下JavaScript测试框架Jasmine。和其他的单元测试框架(比如JUnit等)类似,编写单元测试并不复杂,困难的是弄清楚被测试对象的输入输出,这里就不赘述了。在编写测试用例过程中碰到一个难题:某些JavaScript方法比较耗时,所以采用了异步方式,但和我们平常使用回调函数的方式不同,通过发送系统消息的方式通知,系统中定义了很多消息代码,通过消息代码而得知JavaScript方法执行的结果。因为在实际工作环境中,浏览器经过了定制,为了能够在通用浏览器中演示这一解决方法,下面使用window.postMessage代替实际的代码来说明。

function async_call(data) {
    window.postMessage(data, '*');
}

解决过程

看到这个方法,首先想到的是注册一个事件监听,在异步方法后面等待一个时间间隔,然后判断接收到的消息:

describe("WrongAsyncTest", function() {
    var code;

    var foo = {
        onmessage: function(e) {
            code = e.data;
        }
    };

    beforeEach(function() {
        window.addEventListener('message', function(e) {
            foo.onmessage(e);
        }, false);
        jasmine.clock().install();
    });

    afterEach(function() {
        jasmine.clock().uninstall();
    });

    it('testAsyncCall', function() {
        async_call('10001');
        jasmine.clock().tick(1000);
        expect(code).toEqual('10001');
    });
});

实际上这是行不通的,虽然我们在async_call后面等待了足够的时间,但我们要知道javascript代码实际上是运行于单线程中的,所以不管在testAsyncCall这个方法中等待多长时间,foo.onmessage都在testAsyncCall执行完成之后才会被调用,所以在expect(code).toEqual(‘10001’)这句断言中,code的值实际上还未定义,得到的结果就是:

Expected undefined to equal '10001'.

此路不通,再看jasmine的文档,其中有一部分讲的就是Asynchronous Support。在jasmine的beforeAll, afterAll, beforeEach, afterEach和it调用中,可以增加一个可选的参数(实际上就是一个回调函数),在异步方法执行完成后,调用这个回调函数,通知jasmine执行下一个测试方法。初听起来似乎有点不知所云,还是用一个例子说明:

describe("Synschronous specs", function() {
    var value;

    beforeEach(function(done) {
        setTimeOut(function() {
            value = 0;
            done();
        }, 1);
    });
    it("first", function() {
        expect(value).toEqual(0);
    });

在这个例子中,beforeEach增加了一个done参数,指示Jasmine在done回调函数没调用前,不执行下一个测试方法,所以first测试会一直等待beforeEach中的定时器超时,执行了done()之后才会得到执行。

解决方案

有了这种机制,针对async_call的测试用例就好写了,代码如下:

describe("AsyncTest", function() {
    var code;

    var foo = {
        setcode: function(e) {
            code = e.data;
        },
        onmessage: function(e) {
            console.log("onmessage");
        }
    }

    window.addEventListener('message', function(e) {
        foo.onmessage(e);
    }, false);

    beforeEach(function(done) {
        spyOn(foo, "onmessage").and.callFake(function(e) {
            foo.setcode(e);
            done();
        });

        async_call('10001');
    });

    it('testAsyncCall', function() {
        expect(foo.onmessage).toHaveBeenCalled();
        expect(code).toEqual('10001');
    });
});

说明,done是beforeAll, afterAll, beforeEach, afterEach和it调用的可选参数,所以并不能在foo.onmessage中调用,于是代码中采用了一个迂回的方案,借助于spyOn来监控函数是否被调用。and.callFake的含义是,在被监控的函数被调用时,将用所提供的函数取代被监控函数。就上面的代码而言,就是foo.onmessage被调用时,使用

        function(e) {
            foo.setcode(e);
            done();
        }

替代这个调用。为什么要增加一个foo.setcode方法,那是因为翻看了jasmine文档,没有找到被监控函数调用后,附加执行一个函数的方法,所以增加了一个foo.setcode方法。

总结

Jasmine对javascript同步和异步方法测试都提供了良好的支持,可以用来编写JS单元测试用例。借助第三方js扩展库,还可以测试DOM事件、将测试结果保存到文件等等,这等待下次进一步分析。

上述示例代码可以在我的github项目jsdemo中找到。

参考文档

1. Jasmine官方文档
2. Using Jasmine 2.0’s New done() Function to Test Asynchronous Processes

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云水木石

但行好事,莫问前程

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值