JavaScript 单元测试中 Proxy(代理)和 Mock(打桩)组件的实现

2年多没更新博客了,似乎进了某团队之后就没什么时间深入专研技术了,现在终于可以挤出些时间总结一下工作中遇到的技术问题了。

单元测试的概念和作用就不多说了,本文主要说说单元测试的打桩。

单元测试的粒度通常是函数级,而大部分业务类代码不可能只有一个函数,因此我们在进行单元测试的时候就要对每个函数进行独立的测试,但是对单一函数进行测试的时候不可避免会调用到其他函数和接口,为了简化测试用例的编写和测试环境的搭建,我们通常“假定”调用的其他函数和外部接口都是“正确”的并且可以根据输入返回“预期”的结果,这就需要 Mock(打桩)方式来对这些外部函数和接口进行处理,来模拟我们希望的处理方式,简化其内部实现。而在单元测试过程中,有时需要统计某个外部接口被调用的次数、每次调用的参数、返回值等信息,这时就需要通过 Proxy(代理)的方式将原接口“打包”一下,做个代理。

JavaScript 的灵活性使得代理和打桩都异常简单:写一个函数,在其中调用被代理函数并记录参数、调用次数等信息,再将这个函数赋给被代理的函数,齐活了;打桩更是简单,直接将桩函数赋给原函数,当然也可以轻松的结合代理函数来处理。一个比较通用的原型如下:

function  bind(fn) {
    
var  newFn  =   function  () {
        
var  args  =  arguments;
        
var  index  =  newFn.__ProxyMockData;
        newFn.__ProxyMockData.count
++ ;
        
var  info  =  {
            caller : newFn.caller,
            args : args,
            time : 
- 1
        };
        newFn.__ProxyMockData.info.push(info);
        
var  time  =  ( new  Date()).getTime();
        
var  res  =  newFn.__ProxyMockData.ori.apply( this , args);
        info.time 
=  ( new  Date()).getTime()  -  time;
        info.res 
=  res;
        
return  res;
    };
    newFn.__ProxyMockData 
=  {
        ori : fn,
        count : 
0 ,
        info : []
    };
    
return  newFn;
}

 使用方法也很简单:

function  test() {
    alert(
' test ' );
}
test(); 
//  alert: test

test 
=  bind(test);
test(); 
//  alert: test

//  打桩
test.__ProxyMockData.ori  =   function  () {
    alert(
' mock ' );
};
test(); 
//  alert: mock

alert(
' test 函数调用次数:  '   +  test.__ProxyMockData.count);  //  alert: 2, bind 之前的调用不计算

当然,作为完整的组件,我们方然要把打桩、获取调用信息的过程进行一次封装而避免外部直接使用 __ProxyMockData,而且在打桩前也要记录下原函数,避免原函数丢失无法恢复,同时也可以提供 unbind 方法恢复被代理的函数。

这份代码有问题么?似乎没啥问题。能解决所有问题么?似乎也不能,试想这样一段代码:

function  test(msg) {
    
this .msg  =  msg;
}
test.prototype.test 
=   function  () {
    alert(
' test:  '   +   this .msg);
};
var  t1  =   new  test( ' t1 ' );
t1.test();

test 
=  bind(test);
var  t2  =   new  test( ' t2 ' );
t2.test(); 
//  报错了!

//  打桩
test.__ProxyMockData.ori  =   function  (msg) {
    
this .msg  =   ' mock:  '   +  msg;
};
var  t3  =   new  test( ' t3 ' );
t3.test(); 
//  报错了!

两处报错,原因皆为“ Uncaught TypeError: Object [object Object] has no method 'test' ”,原来,我们创建了代理函数(newFn)时并没有把原函数(fn)的 prototype 原型透传,导致 newFn 的实例并没有 test() 原型。找到了问题的根源就好解决问题了,在创建代理函数中,把原函数中的原型进行透传即可,使用 for in 即可轻松实现,而且 for in 会自动屏蔽 constructor 这个特殊的构造函数(当然以防万一也可以显式做个屏蔽)。另外,在透传的时候也可以再次调用 bind 方法,将原型中的成员函数做个代理再传给代理函数。OK,相信看到这你应该已经晕了,一会直接看代码慢慢理解吧。

当然,对代理函数我们要使用 new 来实例化,而在代理函数的过程中,我们却使用了 newFn.__ProxyMockData.ori.apply 这种相当于直接调用函数的方式,没关系,我们已经把 this 透传到原函数,原函数可以正常的对 this 进行必要的处理,而且构造函数返回 undefined(相当于没有返回)也是没有问题的,因为 JavaScript 引擎会自动判断,如果返回的值不是当前类的实例则直接使用 this 当作返回值。最终的代理生成函数和相关测试函数为:

function  bind(fn) {
    
var  newFn  =   function  () {
        
var  args  =  arguments;
        
var  index  =  newFn.__ProxyMockData;
        newFn.__ProxyMockData.count
++ ;
        
var  info  =  {
            caller : newFn.caller,
            args : args,
            time : 
- 1
        };
        newFn.__ProxyMockData.info.push(info);
        
var  time  =  ( new  Date()).getTime();
        
var  res  =  newFn.__ProxyMockData.ori.apply( this , args);
        info.time 
=  ( new  Date()).getTime()  -  time;
        info.res 
=  res;
        
return  res;
    };
    newFn.__ProxyMockData 
=  {
        ori : fn,
        count : 
0 ,
        info : []
    };
    
for  ( var  key  in  fn.prototype) {
        newFn.prototype[key] 
=  bind(fn.prototype[key]);
    }
    
return  newFn;
}

function  test(msg) {
    
this .msg  =  msg;
}
test.prototype.test 
=   function  () {
    alert(
' test:  '   +   this .msg);
};
var  t1  =   new  test( ' t1 ' );
t1.test(); 
//  test: t1

test 
=  bind(test);
var  t2  =   new  test( ' t2 ' );
t2.test(); 
//  test: t2

//  打桩
test.__ProxyMockData.ori  =   function  (msg) {
    
this .msg  =   ' mock:  '   +  msg;
};
var  t3  =   new  test( ' t3 ' );
t3.test(); 
//  test: mock: t3

//  打桩
test.prototype.test.__ProxyMockData.ori  =   function  (msg) {
    alert(
' mock:  '   +   this .msg);
};
var  t4  =   new  test( ' t4 ' );
t4.test(); 
//  mock: mock: t4

 

OK,不知道头晕的同学有没有缓过来,欢迎大家与我交流。

转载于:https://www.cnblogs.com/hust21941/archive/2010/12/05/js-proxy-mock.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值