主要参考这篇博客,配置了mocha,对文中废弃的写法进行了修改,并用一个demo做了component的单元测试,包含input、button的相关行为,写法其实与Angular自带的jasmine+Karma一样,只是换了一个测试框架。
install:
- mocha (test runner)
- mocha-webpack (build the tests with Webpack before running them)
- chai (assertion library)
- @types/chai (TypeScript definitions for chai)
- sinon (mocking library)
- @types/sinon (TypeScript definitions for sinon)
- webpack-node-externals (exclude server-side node modules from the build)
- jsdom (provides stubs for DOM API required by Angular - HTMLElement, XMLHttpRequest)
npm install --save-dev mocha mocha-webpack chai @types/chai sinon @types/sinon webpack-node-externals jsdom
目前mocha-webpack只支持mocha5以下,所以可能会提示:
npm WARN [email protected] requires a peer of mocha@>=2.4.5 <=5 but none is installed. You must install peer dependencies yourself.
npm WARN [email protected] requires a peer of webpack@^2.0.0 || ^3.0.0 but none is installed. You must install peer dependencies yourself.
手动把设mocha-webpack与mocha的版本为:
"mocha": "5.0.0",
"mocha-webpack": "2.0.0-beta.0",
config
- 在项目目录中添加config文件夹,与src同级,创建下面四个文件:
- helpers.js
- mocha-test-shim.js
- webpack-mocha.js
- webpack-test.js
- helpers.js:
var path = require('path');
var _root = path.resolve(__dirname, '..');
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [_root].concat(args));
}
exports.root = root;
- mocha-test-shim.js
这个文件改动较大。改动如下:
- 注意顺序,require一定要放在最前面,zone的顺序最好不要乱,否则可能报错,而且测试中不能用async,否则会报ProxZone之类的错,所以对于async的测试怎么办还需要想办法;
- 原来的
require('zone.js/dist/zone')
需要换成require('zone.js/dist/zone-node')
,否则会报Zone 的reference error; - jsdom的写法改变了,测试文件中用的document、node、event等全部要在这里设定,通过global.xxx = window.xxx的方式,否则会出现xxx is not defined的错误。
改动后的内容为:
require('core-js/es6');
require('core-js/es7/reflect');
require('zone.js/dist/zone-node');
require('zone.js/dist/long-stack-trace-zone');
require('zone.js/dist/proxy');
require('zone.js/dist/sync-test');
require('zone.js/dist/async-test');
require('zone.js/dist/fake-async-test');
var jsdom = require('jsdom')
const { JSDOM } = jsdom;
var document = new JSDOM('');
global.window = document.window;
global.document = document.window.document;
global.HTMLElement = window.HTMLElement;
global.XMLHttpRequest = window.XMLHttpRequest;
global.Node = window.Node;
global.Event = window.Event;
// global.Text = window.Text;
// global.DOMTokenList = window.DOMTokenList;
var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
- webpack-test.js
这个文件也有改动:
-
loaders
换成rules
; - 所有的loader都要写全称,并且用npm进行安装;
npm install --save awesome-typescript-loader angular2-template-loader html-loader null-loader raw-loade
改动后的内容如下:
var helpers = require('./helpers');
module.exports = {
devtool: 'cheap-module-source-map',
resolve: {
extensions: ['*', '.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loader: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'null-loader'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'app'),
loader: 'null-loader'
},
{
test: /\.css$/,
include: helpers.root('src', 'app'),
loader: 'raw-loader'
}
]
}
}
- 在与src同级的目录中新建
mocha-webpack.opts
文件,主要用来设置webpack的,这样就不需要每次在命令行里输入所传的参数。文件内容如下:
--webpack-config config/webpack.mocha.js webpack --mode=development
--require config/mocha-test-shim.js
src/**/*.spec.ts
在scripts中加入:
"scripts": {
...
"test": "mocha-webpack",
...
},
demo
使用与Angular Form中相同的demo,在其基础上添加测试,这里主要抽象出对于input以及button等控件的测试,具体代码可以到repo来看。
设置TestBed,需要把用到的Module写到imports中
beforeEach((() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
imports: [
ReactiveFormsModule,
FormsModule,
],
}).compileComponents();
}));
afterEach(() => {
getTestBed().resetTestingModule();
});
获取fixture以及component
也可以写在before里,复用,根据需求来。
const fixture = TestBed.createComponent(AppComponent);
const component = fixture.componentInstance;
获取html的dom元素:
querySelector中获取元素的方式与css一样,css的selector都可以用。
const nameInput = fixture.nativeElement.querySelector('#nameInput');
const zodiacSignInput = fixture.nativeElement.querySelector('#zodiacSignInput');
每次对内容进行修改后,记得fixture.detectChanges
有时候测试挂掉是因为没有detectChanges或者detectChanges的位置没有放对。
...
component.editMode = true;
fixture.detectChanges();
...
写断言
这里用的chai。
expect(showNameInput.value).to.equal('Kate');
创建Event来模拟用户输入
注意,如果这里有Event,就要在mocha-test-shim.js
中加global.Event=window.Event
。
...
const event = new Event('input');
nameInput.value = 'Kate';
zodiacSignInput.value = 'Cancer';
nameInput.dispatchEvent(event);
zodiacSignInput.dispatchEvent(event);
fixture.detectChanges();
...
常见Error:
-
"before each" hook for "xxx":
** Error: Expected to be running in 'ProxyZone', but it was not found.**
解决:检查test中是否有async,如果有,删掉。
- Zone.__load_patch('ZoneAwarePromise', function (global, Zone, api) {
^
ReferenceError: Zone is not defined
解决:将mocha-test-shim.js
文件中所有require的移动到最上边,zone换成zone-node - ReferenceError: Event is not defined
at Context.
解决:在mocha-test-shim.js
中加global.Event=window.Event
repo:https://github.com/LiuKaixinHappy/angular-form-demo
好的mock store的博客:
- https://medium.com/@bo.vandersteene/mock-your-ngrx-store-on-the-easy-way-68c66d4bea63