我正在尝试测试使用jasmine通过$document
服务对DOM进行一些操作的角度服务。
假设它只是将一些指令附加到<body>
元素。
此类服务可能看起来像
(function(module) {
module.service('myService', [
'$document',
function($document) {
this.doTheJob = function() {
$document.find('body').append('<my-directive></my directive>');
};
}
]);
})(angular.module('my-app'));
我想像这样测试它
describe('Sample test' function() {
var myService;
var mockDoc;
beforeEach(function() {
module('my-app');
// Initialize mock somehow. Below won't work indeed, it just shows the intent
mockDoc = angular.element('<html><head></head><body></body></html>');
module(function($provide) {
$provide.value('$document', mockDoc);
});
});
beforeEach(inject(function(_myService_) {
myService = _myService_;
}));
it('should append my-directive to body element', function() {
myService.doTheJob();
// Check mock's body to contain target directive
expect(mockDoc.find('body').html()).toContain('<my-directive></my-directive>');
});
});
所以问题是创建这样的模拟的最佳方法是什么?
使用真实document
进行测试会给我们在每次测试后清理时带来很多麻烦,并且看起来不像是一种方法。
我还尝试在每次测试之前创建一个新的真实文档实例,但结果却出现了不同的失败。
创建如下所示的对象并检查whatever
变量是否有效,但看起来非常难看
var whatever = [];
var fakeDoc = {
find: function(tag) {
if (tag == 'body') {
return function() {
var self = this;
this.append = function(content) {
whatever.add(content);
return self;
};
};
}
}
}
我觉得我错过了一些重要的事情并做了一些非常错误的事情。
非常感谢任何帮助。
答案 0 :(得分:17)
在这种情况下,您无需模拟$document
服务。使用它的实际实现更容易:
describe('Sample test', function() {
var myService;
var $document;
beforeEach(function() {
module('plunker');
});
beforeEach(inject(function(_myService_, _$document_) {
myService = _myService_;
$document = _$document_;
}));
it('should append my-directive to body element', function() {
myService.doTheJob();
expect($document.find('body').html()).toContain('<my-directive></my-directive>');
});
});
Plunker here。
如果你真的需要嘲笑它,那么我想你必须按照你的方式去做:
$documentMock = { ... }
但这可能会破坏依赖$document
服务本身的其他事物(例如,使用createElement
的指令)。
的更新强>
如果您需要在每次测试后将文档恢复到一致状态,您可以执行以下操作:
afterEach(function() {
$document.find('body').html(''); // or $document.find('body').empty()
// if jQuery is available
});
Plunker here(我必须使用另一个容器,否则不会呈现Jasmine结果)。
正如@AlexanderNyrkov在评论中指出的那样,Jasmine和Karma在body
标签中都有自己的东西,并且通过清空文档主体来消除它们似乎不是一个好主意。
更新2
我设法部分模拟了$document
服务,因此您可以使用实际的页面文档并将所有内容恢复到有效状态:
beforeEach(function() {
module('plunker');
$document = angular.element(document); // This is exactly what Angular does
$document.find('body').append('<content></content>');
var originalFind = $document.find;
$document.find = function(selector) {
if (selector === 'body') {
return originalFind.call($document, 'body').find('content');
} else {
return originalFind.call($document, selector);
}
}
module(function($provide) {
$provide.value('$document', $document);
});
});
afterEach(function() {
$document.find('body').html('');
});
Plunker here。
我们的想法是将body
标签替换为您的SUT可以自由操作的新标签,并且您的测试可以在每个规范的最后安全地清除。
答案 1 :(得分:6)
您可以使用DOMImplementation#createHTMLDocument()
:
describe('myService', function() {
var $body;
beforeEach(function() {
var doc;
// Create an empty test document based on the current document.
doc = document.implementation.createHTMLDocument();
// Save a reference to the test document's body, for asserting
// changes to it in our tests.
$body = $(doc.body);
// Load our app module and a custom, anonymous module.
module('myApp', function($provide) {
// Declare that this anonymous module provides a service
// called $document that will supersede the built-in $document
// service, injecting our empty test document instead.
$provide.value('$document', $(doc));
});
// ...
});
// ...
});
因为您要为每个测试创建一个新的空文档,所以您不会干扰运行测试的页面,并且您不必在测试之间的服务之后明确清理。