Angular js unit test mock document
angularjs, document, javascript, testing, unit-testing
Solution
You don't need to mock the `$document` service in such a case. It's easier just to use its actual implementation:
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.
If you really need to mock it out, then I guess you'll have to do it the way you did:
$documentMock = { ... }
But that can break other things that rely on the `$document` service itself (such a directive that uses `createElement`, for instance).
UPDATE
If you need to restore the document back to a consistent state after each test, you can do something along these lines:
afterEach(function() {
$document.find('body').html(''); // or $document.find('body').empty()
// if jQuery is available
});
Plunker here (I had to use another container otherwise Jasmine results wouldn't be rendered).
As @AlexanderNyrkov pointed out in the comments, both Jasmine and Karma have their own stuff inside the `body` tag, and wiping them out by emptying the document body doesn't seem like a good idea.
UPDATE 2
I've managed to partially mock the `$document` service so you can use the actual page document and restore everything to a valid state:
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.
The idea is to replace the `body` tag with a new one that your SUT can freely manipulate and your test can safely clear at the end of every spec.
Problem
I am trying to test angular service which does some manipulations to DOM via `$document` service with jasmine. Let's say it simply appends some directive to the `<body>` element. Such service could look like ``` (function(module) { module.service('myService', [ '$document', function($document) { this.doTheJob = function() { $document.find('body').append('<my-directive></my directive>'); }; } ]); })(angular.module('my-app')); ``` And I want to test it like this ``` 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>'); }); }); ``` So the question is what would be the best way to create such mock? Testing with real `document` will give us much trouble cleaning up after each test and does not look like a way to go with. I've also tried to create a new real document instance before each test, yet ended up with different failures. Creating an object like below and checking `whatever` variable works but looks very ugly ``` var whatever = []; var fakeDoc = { find: function(tag) { if (tag == 'body') { return function() { var self = this; this.append = function(content) { whatever.add(content); return self; }; }; } } } ``` I feel that I'm missing something important here and doing something very wrong. Any help is much appreciated.