用karma对基于cmd(seajs)的前端代码进行单元测试

近期做前端单元测试,因为用了seajs,项目中很多定义了匿名的cmd(根据文件名、路径加载),但在用karma测试的过程中遇到了点麻烦,这个blog就总结下解决的问题。

1. 首先要先了解下karma的script加载方式

    在karma.conf.js的配置里files都会以<script>标签加载在karma的context.html页面上(即测试页面中)。所以我们要测试的依赖文件、单元测试文件都要写在files里。karma提供了以/base /absolute开头的url路径访问files。所以我们在files里配置的路径,对应karma的浏览器路径就是

/base/yourfile.js

(因为对karma了解得还很少,所以有错误的地方童鞋请指出)

2. 要解决匿名cmd的问题

    如果你有一个file,例子a.js,里面这么定义

// a.js
define(function(require, exports, module){
	****
});

   另一个b.js里这么引用require('/absolutepath/a.js')

在karma里就出现问题了,因为karma引入<script file="a.js">执行下,对于seajs来说只是定义一个匿名的cmd,真正用的时候,路径就变成了/base/a.js。所以b.js在运行时候会找不到a——测试不能修改已经完成的开发的代码。

为了解决这个,思路可以有两个

    a) 第一就是在测试时候动态修改b.js,修改require的路径,这个不合适,因为有的是引用了已经定义好name的cmd,而不是用路径。无法做到一致修改。

    b) 第二个就是把匿名的cmd,在karma里变成用路径作为其name的cmd——百度之,有一个karma-commonjs的plugin,看了下不适合,但给了思路,用preprocessor,所以模仿写一个,就是替换define(function{而已

var createPreprocesor = function(basePath, logger) {
  var log = logger.create('preprocessor.commonjs');

  return function(content, file, done) {
	var pat = /(define *\(( *function.+))/;
	var mat = pat.exec(content);
    if (!mat) {
      return done(content);
    }

	var id = file.path.replace(basePath, '');
    var output = content.replace(mat[1], "define('" + id + "'," + mat[2]);

    done(output);
  };
};

createPreprocesor.$inject = ['config.basePath', 'logger'];

// PUBLISH DI MODULE
module.exports = {
  'preprocessor:commonjs': ['factory', createPreprocesor]
};

这样原本的a.js就变成了

// a.js
define('/js/a.js', function(require, exports, module){
	****
});

3. 需要对cmd的name进行重新定义——由于这个preprocessor写得很简单,就是把file path相对basePath的路径做为cmd的name。所以还是解决不了b.js以相对路径引入a.js的问题。

重新定义了还有个必要性就是b.js即便用绝对路径引入了a.js,但如果测试代码依赖需要b.js的执行完成,b.js用seajs的load /base/js/a.js,会出现不同步问题。seajs.use('/js/b.js')虽然执行完下了,b.js里的内容还没执行完,测试内容执行就报错了。

所以karma的files再增加一个这样的file

// redefine
seajs.use('/js/a.js', function(obj){
	// obj is a function
	/*
	define('a.js', function(){
		return obj;
	});
	*/
	define('a.js', obj);
});

到此位置就基本上可以测试大多数物理文件上分散的seajs的文件了,如果有require嵌套很深的,就要有多个这样的redefine.js,加载顺序还要固定。

具体的测试代码大概是这样的

seajs.use('/js/b.js');

describe('Test http', function() {
	describe('MyCtrl',function(){
		beforeEach(module('md'));
		var scope, ctrl;
		beforeEach(inject(function($rootScope, $controller){
			scope = $rootScope.$new();
			ctrl = $controller('MyCtrl',{$scope: scope});
		}));
		it('init data', function(){
			expect(scope.val).toBe('123');
		});
	});
});

相关推荐