2

介绍

使用 karma + Jasmine,就可以测试指定的案例, 加入RequireJS, 可以进行测试异步模块的案例

安装

# Install Karma:
$ npm install karma --save-dev

# Install plugins that your project needs:
$ npm install karma-jasmine karma-chrome-launcher jasmine-core --save-dev

# Install karma-requirejs
$ npm install requirejs --save-dev
$ npm install karma-requirejs --save-dev

全局配置karma 命令行

npm install -g karma-client

配置

module.exports = function (config) {
  config.set({
    // ...
    frameworks: ['jasmine', 'requirejs'],
    files: [
      {pattern: 'lib/**/*.js', included: false},
      {pattern: 'src/**/*.js', included: false},
      {pattern: 'test/**/*[sS]pec.js', included: false},
      'test-main.js'
    ]
    // ...
  })
}

加载karma-jasmine, karma-requirejs 框架, 引入 test-main.js. files

test-main.js 介绍

var allTestFiles = []

// 测试文件
var TEST_REGEXP = /(spec|test)\.js$/i

// Get a list of all the test files to include
// 解析files 和 frameworks 对应需要加载的文件
Object.keys(window.__karma__.files).forEach(function (file) {

  if (TEST_REGEXP.test(file)) {
    // Normalize paths to RequireJS module names.
    // If you require sub-dependencies of test files to be loaded 
    // as-is (requiring file extension)
    // then do not normalize the paths
    // 将符合条件的测试文件转化为 requirejs 能加载的模块名
    var normalizedTestModule = file.replace(/^\/base\/|\.js$/g, '')
    allTestFiles.push(normalizedTestModule)
  }
})

require.config({
  // Karma serves files under /base, which is the basePath from your config file
  // karma 将basePath 对应的静态路径设置为 /base
  // requirejs 加载文件也应当由 /base 开始
  baseUrl: '/base',

  // dynamically load all test files
  // 动态加载测试文件
  deps: allTestFiles,

  // we have to kickoff jasmine, as it is asynchronous
  // deps 加载完后 执行 单元测试
  callback: window.__karma__.start
})

简单demo

目录结构

- test
- src
- lib
- test-main.js
- index.html
- main.js

源文件 src/utils/qs.js

define(function () {

  var encodeURIComponent = window.encodeURIComponent;
  var decodeURIComponent = window.decodeURIComponent;

  function isArray (o) {
    return Object.prototype.toString.call(o) === '[object Array]';
  }
 
  function isObject (o) {
    return Object.prototype.toString.call(o) === '[object Object]';    
  }
  
  return {
    parse: function (url) {

      if (typeof url !== 'string') {
        throw new TypeError('qs.parse() Error, url should transmit a string param');
      }

      var result = {};
      url = url.substr(url.indexOf('?') + 1);
      var querystring = url.replace(/#.*/, '');
      var patterns = querystring.split('&');

      patterns.forEach(function(pattern) {
        if (!pattern) {
          return;
        }
        var matched = pattern.match(/([^=]+)=(.*)/);
        if (!matched) {
          return;
        }
        var key = matched[1],
          value = matched[2],
          isArr = false;

        if (key.slice(-2) === '[]') {
          key = key.slice(0, -2);
          isArr = true;
        }

        key = decodeURIComponent(key);
        value = decodeURIComponent(value);

        if (isArr) {
          if (!Array.isArray(result[key])) {
            result[key] = [];    
          }
          result[key].push(value);
        } else {
          result[key] = value;
        }
      }, this);

      return result;
    },

    stringify: function (obj) {

      if (!isObject(obj)) {
        throw new TypeError('qs.stringify() Error, Unexpected obj is not Object');
      }

      return Object.keys(obj).map(function (name, index) {
        if (isArray(obj[name])) {
          return obj[name].map(function (item) {
            return encodeURIComponent(name) + '[]=' + encodeURIComponent(item); 
          }).join('&');
        }
        return encodeURIComponent(name) + '=' + encodeURIComponent(obj[name]);
      }).join('&');
    }
  }
});

测试文件 test/utils/qsSpec.js

define(['src/utils/qs'], function(qs) {

  beforeAll(function () {
    spyOn(qs, 'parse').and.callThrough();
    spyOn(qs, 'stringify').and.callThrough();
  });

  describe('qs.parse() suite', function() {

    it('should throw TypeError', function() {
      expect(qs.parse).toThrowError(TypeError);
    });

    it('should parse to object', function () {
      var url = 'http://liylblog.com/?a=1&b=2&c=3';
      expect(qs.parse(url)).toEqual({
        a: '1',
        b: '2',
        c: '3'
      });
    });

    it('should parse to object array', function () {
      var url = 'http://liylblog.com/?a[]=1&a[]=2&a[]=3';
      expect(qs.parse(url)).toEqual({
        a: ['1', '2', '3']
      });
    });

    it('should be decodeURIComponent', function () {
      var aVal = window.encodeURIComponent('=1');
      var bVal = window.encodeURIComponent('?2');
      var url = 'http://liylblog.com/?a=' + aVal + '&b=' + bVal + '&c=3#';
      expect(qs.parse(url)).toEqual({
        a: '=1',
        b: '?2',
        c: '3'
      });
    });

  });

  describe('qs.stringify() suite', function () {
    var obj;

    beforeEach(function () {
      obj = {
        a: 1,
        b: ['你好', '世界'],
        c: ['2', null, '']
      };
    });
    
    afterEach(function () {
      obj = null;
    });

    it('should throw TypeError', function() {
      expect(qs.stringify).toThrowError(TypeError);
    });

    it('should stringify obj', function () {
      var querystring = qs.stringify(obj); 
      var containB = (function () {
        var str = ''
        for (var i = 0, len = obj['b'].length; i < len; i++) {
          str += i > 0 ? '&' : ''
          str += 'b[]=' + encodeURIComponent(obj['b'][i])
        }
        return str;
      })();
      var containC = 'c[]=null';

      expect(querystring).toContain('a=1')
      expect(querystring).toContain(containB);
      expect(querystring).toContain(containC);
    });

  });
});

执行

karma start ./karma.conf.js


wayneli
1.4k 声望828 粉丝

2017-2018年目标