使用 Jest 进行单元测试 AngularJS 指令
Unit testing AngularJS Directives with Jest
我觉得我在这个极其简化的 angular 指令单元测试中遗漏了一些重要的东西:
import * as angular from 'angular'
import 'angular-mocks'
const app = angular.module('my-app', [])
app.directive('myDirective', () => ({
template: 'this does not work either',
link: (scope, element) => { // have also tried compile fn
console.log('This does not log')
element.html('Hi!')
}
}))
describe('myDirective', () => {
var element, scope
beforeEach(app)
beforeEach(inject(($rootScope, $compile) => {
scope = $rootScope.$new()
element = $compile('<my-directive />')(scope)
scope.$digest()
}))
it('should actually do something', () => {
expect(element.html()).toEqual('Hi!')
})
})
当 jest 运行时,指令似乎没有被 linked/compiled/whatever
FAIL test/HtmlToPlaintextDirective.spec.js
● myDirective › should actually do something
expect(received).toEqual(expected)
Expected value to equal:
"Hi!"
Received:
""
更新后的答案:
你是对的,当在单个文件中导入所有内容时,事情并没有按预期工作。
深入研究您似乎 运行 的一些魔法 Babel/Jest 确实支持依赖全局变量的浏览器脚本(如 AngularJS)。
你的模块的 angular
变量 与 angular 可见的全局 angular
变量不同-嘲笑。
您可以通过 运行 在您的一项测试的顶部进行检查:
import * as angular from 'angular'
import 'angular-mocks'
console.log(angular === window.angular); // `false` in Jest!
console.log(angular.mock); // undefined
console.log(window.angular.mock); // `{...}` defined
要解决此问题,您只需在测试中使用全局 angular
变量。
src/__test__/all-in-one.test.js:
import "angular";
import "angular-mocks";
/*
Work around Jest's window/global mock magic.
Use the global version of `angular` that has been augmented by angular-mocks.
*/
var angular = window.angular;
export var app = angular.module('app', []);
app.directive('myDirective', () => ({
link: (scope, element) => {
console.log('This does log');
scope.content = 'Hi!';
},
template: 'content: {{content}}'
}));
describe('myDirective', function(){
var element;
var scope;
beforeEach(function(){
angular.mock.module(app.name);
});
it('should do something', function(){
inject(function(
$rootScope,
$compile
){
scope = $rootScope.$new();
element = $compile('<my-directive></my-directive>')(scope);
scope.$digest();
});
expect(element.html()).toEqual('content: Hi!');
});
});
原答案:(之所以有效,是因为我在测试中不小心使用了 angular
的全球版本。)
测试中的 Angular 模块未在您的测试中正确初始化。
您对 beforeEach(app)
的调用不正确。
相反,您需要使用 angular.mock.module("moduleName")
来初始化您的模块。
describe('myDirective', () => {
var element, scope
// You need to pass the module name to `angular.mock.module()`
beforeEach(function(){
angular.mock.module(app.name);
});
// Then you can set up and run your tests as normal:
beforeEach(inject(($rootScope, $compile) => {
scope = $rootScope.$new()
element = $compile('<my-directive></my-directive>')(scope)
scope.$digest()
}))
it('should actually do something', () => {
expect(element.html()).toEqual('Hi!')
})
});
然后你的测试按我的预期工作:
PASS src\__test__\app.test.js
myDirective
√ should do something (46ms)
作为参考,这里是完整的应用程序和测试:
src/app/app.module.js:
import * as angular from 'angular'
export var app = angular.module('app', []);
app.directive('myDirective', () => ({
link: (scope, element) => {
console.log('This does log');
scope.content = 'Hi!';
},
template: 'content: {{content}}'
}))
src/__test__/app.test.js:
import {app} from "../app/app.module";
import "angular-mocks";
describe('myDirective', function(){
var element;
var scope;
beforeEach(function(){
angular.mock.module(app.name);
});
beforeEach(inject(function(
$rootScope,
$compile
){
scope = $rootScope.$new();
element = $compile('<my-directive></my-directive>')(scope);
scope.$digest();
}));
it('should do something', function(){
expect(element.html()).toEqual('content: Hi!');
});
});
几年后我 运行 遇到了同样令人困惑的行为,我想分享我的发现
如果你运行使用 babel 拼写测试并查看导入你会发现类似于下面的内容
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var angular = _interopRequireWildcard(require("angular"));
require("angular-mocks");
_interopRequireWildcard
目前有以下实现
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
简而言之,它 创建一个新对象 并从导入的对象复制所有属性。这就是 angular === window.angular
是 false
的原因。它还解释了为什么 angular.mock
没有定义,当 _interopRequireWildcard
复制模块
时它不存在
考虑到除了已接受的答案之外,还有一些其他方法可以解决问题
而不是使用 import * as angular from 'angular'
使用 import angular from 'angular'
应该避免这种行为,因为 _interopRequireDefault
不会 return 不同的对象。 (但是,如果您使用的是 TypeScript,则使用此方法可能无法正确解析 'angular' 的类型)
另一种选择是导入 angular 两次:
import 'angular'
import 'angular-mocks'
import * as angular from 'angular'
我觉得我在这个极其简化的 angular 指令单元测试中遗漏了一些重要的东西:
import * as angular from 'angular'
import 'angular-mocks'
const app = angular.module('my-app', [])
app.directive('myDirective', () => ({
template: 'this does not work either',
link: (scope, element) => { // have also tried compile fn
console.log('This does not log')
element.html('Hi!')
}
}))
describe('myDirective', () => {
var element, scope
beforeEach(app)
beforeEach(inject(($rootScope, $compile) => {
scope = $rootScope.$new()
element = $compile('<my-directive />')(scope)
scope.$digest()
}))
it('should actually do something', () => {
expect(element.html()).toEqual('Hi!')
})
})
当 jest 运行时,指令似乎没有被 linked/compiled/whatever
FAIL test/HtmlToPlaintextDirective.spec.js
● myDirective › should actually do something
expect(received).toEqual(expected)
Expected value to equal:
"Hi!"
Received:
""
更新后的答案:
你是对的,当在单个文件中导入所有内容时,事情并没有按预期工作。
深入研究您似乎 运行 的一些魔法 Babel/Jest 确实支持依赖全局变量的浏览器脚本(如 AngularJS)。
你的模块的 angular
变量 与 angular 可见的全局 angular
变量不同-嘲笑。
您可以通过 运行 在您的一项测试的顶部进行检查:
import * as angular from 'angular'
import 'angular-mocks'
console.log(angular === window.angular); // `false` in Jest!
console.log(angular.mock); // undefined
console.log(window.angular.mock); // `{...}` defined
要解决此问题,您只需在测试中使用全局 angular
变量。
src/__test__/all-in-one.test.js:
import "angular";
import "angular-mocks";
/*
Work around Jest's window/global mock magic.
Use the global version of `angular` that has been augmented by angular-mocks.
*/
var angular = window.angular;
export var app = angular.module('app', []);
app.directive('myDirective', () => ({
link: (scope, element) => {
console.log('This does log');
scope.content = 'Hi!';
},
template: 'content: {{content}}'
}));
describe('myDirective', function(){
var element;
var scope;
beforeEach(function(){
angular.mock.module(app.name);
});
it('should do something', function(){
inject(function(
$rootScope,
$compile
){
scope = $rootScope.$new();
element = $compile('<my-directive></my-directive>')(scope);
scope.$digest();
});
expect(element.html()).toEqual('content: Hi!');
});
});
原答案:(之所以有效,是因为我在测试中不小心使用了 angular
的全球版本。)
测试中的 Angular 模块未在您的测试中正确初始化。
您对 beforeEach(app)
的调用不正确。
相反,您需要使用 angular.mock.module("moduleName")
来初始化您的模块。
describe('myDirective', () => {
var element, scope
// You need to pass the module name to `angular.mock.module()`
beforeEach(function(){
angular.mock.module(app.name);
});
// Then you can set up and run your tests as normal:
beforeEach(inject(($rootScope, $compile) => {
scope = $rootScope.$new()
element = $compile('<my-directive></my-directive>')(scope)
scope.$digest()
}))
it('should actually do something', () => {
expect(element.html()).toEqual('Hi!')
})
});
然后你的测试按我的预期工作:
PASS src\__test__\app.test.js
myDirective
√ should do something (46ms)
作为参考,这里是完整的应用程序和测试:
src/app/app.module.js:
import * as angular from 'angular'
export var app = angular.module('app', []);
app.directive('myDirective', () => ({
link: (scope, element) => {
console.log('This does log');
scope.content = 'Hi!';
},
template: 'content: {{content}}'
}))
src/__test__/app.test.js:
import {app} from "../app/app.module";
import "angular-mocks";
describe('myDirective', function(){
var element;
var scope;
beforeEach(function(){
angular.mock.module(app.name);
});
beforeEach(inject(function(
$rootScope,
$compile
){
scope = $rootScope.$new();
element = $compile('<my-directive></my-directive>')(scope);
scope.$digest();
}));
it('should do something', function(){
expect(element.html()).toEqual('content: Hi!');
});
});
几年后我 运行 遇到了同样令人困惑的行为,我想分享我的发现
如果你运行使用 babel 拼写测试并查看导入你会发现类似于下面的内容
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var angular = _interopRequireWildcard(require("angular"));
require("angular-mocks");
_interopRequireWildcard
目前有以下实现
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
} else {
var newObj = {};
if (obj != null) {
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {};
if (desc.get || desc.set) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
}
newObj.default = obj;
return newObj;
}
}
简而言之,它 创建一个新对象 并从导入的对象复制所有属性。这就是 angular === window.angular
是 false
的原因。它还解释了为什么 angular.mock
没有定义,当 _interopRequireWildcard
复制模块
考虑到除了已接受的答案之外,还有一些其他方法可以解决问题
而不是使用 import * as angular from 'angular'
使用 import angular from 'angular'
应该避免这种行为,因为 _interopRequireDefault
不会 return 不同的对象。 (但是,如果您使用的是 TypeScript,则使用此方法可能无法正确解析 'angular' 的类型)
另一种选择是导入 angular 两次:
import 'angular'
import 'angular-mocks'
import * as angular from 'angular'