Angular js unit test mock document - javascript

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.

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.

You can create an empty test document using 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));
});
// ...
});
// ...
});
Because you're creating a new, empty document for each test, you won't interfere with the page running your tests and you won't have to explicitly clean up after your service between tests.

Related

inject utility services to jasmine test

This question is not about including a service to test or to provide a mock that replaces a service.
Situation:
The Factory I'd like to test is about parsing a set of properties and provide this information via getter functions. The following pseudocode describes what is happening right now (supprisingly it works, although it is quite hacky to create dynamically tests depending on the data length.)
...
describe('fancyTest', function() {
// 1
beforeEach(function() {
// 7
module('app');
inject(function($injector) {
// 8
Factory = $injector.get('app.ToTestFactory');
UtilService = $injector.get('app.Util'); // bad, as to late...
});
});
// 2
describe('dataTest', function() {
// 3
// Goal: data = UtilService.getData('dataset1');
data = [{id:'test1'}, {id:'test2'}];
for (i = 0, l = data.length; i < l; i += 1) {
test(data[i]);
}
function test(properties) {
// 4
describe('datatest #' + i, function() {
var elem;
// 5
beforeEach(function() {
// 9
elem = new Factory(properties)
});
// 6
it('should provide the correct id', function() {
// 10
expect(elem.id()).toBe(properties.id);
});
...
});
}
}
...
}
The UtilService.getData() is a simple method that reads the data out of some constants that are only injected when executing tests. It's maybe important, that I dont want to load them asynchronous.
Problem:
The Jasmine framework has a quite uninituitive workflow and first runs and initializes all describe blocks, before it runs through the beforeEach. The order is written in the comments.
Do I have no chance to inject the UtilService before the data-loop runs through?
Thanks for helping me out!
If your UtilService is a factory, you can inject the service into every test with the before each
beforeEach(function() {
module('app');
var UtilService;
inject(function(_UtilService_) {
UtilService = _UtilService_;
});
});
The way jasmine sets up tests, as long as you load all of your dependencies properly in your Karma Config, you shouldn't ever have to use $injector to pull in a service.
And because the underscores are probably a little confusing, angular will provide an underscored service so you can create a new instance every time.

Test a callback method's functionality in Jasmine

I have a service as following.
InvService(...){
this.getROItems = function(cb){
$http.get('url').success(cb);
}
}
One of the controllers which uses the above:
var roItems = [];
InvService.getROItems(function(res){
roItems = res.lts.items;
});
In Jasmine, I want to test that roItems are assigned the values from the response. How can I achieve this?
I'd recommend that you have separated tests for you service and for your controller. If you want to test that roItems was assigned, you need to test your controller. Then, you could mock your service since it is not relevant for the controller test and make it return whatever you want. You need something like this:
describe('my awesome test', function() {
it('my awesome test block',
inject(function(InvService, $controller) {
//This mocks your service with a fake implementation.
//Note that I mocked before the controller initialization.
spyOn(InvService, 'getROItems').and.callFake(function(cb){
var resultFake = {
lts: {
items: "whatever you want"
}
}
cb(resultFake);
});
//This initializes your controller and it will use the mocked
//implementation of your service
var myController = $controller("myControllerName");
//Here we make the assertio
expect(myController.roItems).toBe("whatever you want");
}
)
});

What's the proper way to use this extension in AngularJS code?

I'm using a framework called Radiant UI, which is a way to get HTML5 UI into Unreal Engine 4. I'm trying to pick up some modern Javascript while I do that, so I'm building the UI in AngularJS.
My understanding of Angular is still pretty weak though, and I'm a bit confused about what the best practice is here. The extension injects the following Javascript when it sets up.
var RadiantUI;
if (!RadiantUI)
RadiantUI = {};
(function() {
RadiantUI.TriggerEvent = function() {
native function TriggerEvent();
return TriggerEvent(Array.prototype.slice.call(arguments));
};
RadiantUI.SetCallback = function(name, callback) {
native function SetHook();
return SetHook(name, callback);
};
RadiantUI.RemoveCallback = function(name) {
native function RemoveHook();
return RemoveHook(name);
};
})();;
So this is simply pushing RadiantUI into the global namespace. That would be fine if the extension was always there, but it isn't. In the test environment (Chrome), it's not there. It's only there when running in the game engine. That, combined with the fact that globals suck, means I want to encapsulate it.
In the previous iteration of this, I had it wrapped in an AMD module, and it worked well. Like this:
define([], function()
{
if ("RadiantUI" in window)
{
console.log("RadiantUI in global scope already!");
return window.RadiantUI;
}
var RadiantUI;
if (!RadiantUI) {
RadiantUI = {};
RadiantUI.TriggerEvent = function() {}
RadiantUI.SetCallback = function() {}
RadiantUI.RemoveCallback = function() {}
}
console.log("Using fake RadiantUI bindings");
return RadiantUI;
});
So here's what I want to do:
I want to include radiant as a dependency to my app/stateProvider and have it injected, much the same way it would be in AMD. With the stub methods in place if the extension isn't present. What's the proper approach to this? A module? A service provider?
UPDATE: This is the working code using the answer given.
var myapp = angular.module('bsgcProtoApp', ['ui.router' ]);
myapp.value('radiant', window.RadiantUI || {
TriggerEvent: function()
{
console.log("TriggerEvent called");
},
SetCallback: function(name, callback)
{
console.log("Setcallback called");
},
RemoveCallback: function(name)
{
console.log("RemoveCallback called");
}
});
myapp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider )
{
$urlRouterProvider.otherwise("/mainmenu");
$stateProvider.state('mainmenu',
{
name: "mainmenu",
url: "/mainmenu",
templateUrl: 'templates/mainmenu.html',
controller: ['$scope', 'radiant', function($scope, radiant)
{
$scope.tester = function()
{
radiant.TriggerEvent("DuderDude");
console.log("Duder!");
}
}],
});
}]);
You presumably have an Angular module or app. For the sake of this answer, let's call it MyApp.
Now you can do
MyApp.value("RadiantUI", window.RadiantUI || {
TriggerEvent = function(){},
//... more properties
});
Now to access this value as a dependency in a controller for example, you'd do this
MyApp.controller(["$scope", "RadiantUI", function($scope, RadiantUI){
// ... controller code ...
}]);

QUnit, Sinon.js & Backbone unit test frustration: sinon spy appears to fail to detect Backbone Model event callbacks

In the following unit test code:
TestModel = Backbone.Model.extend({
defaults: {
'selection': null
},
initialize: function() {
this.on('change:selection', this.doSomething);
},
doSomething: function() {
console.log("Something has been done.");
}
});
module("Test", {
setup: function() {
this.testModel = new TestModel();
}
});
test("intra-model event bindings", function() {
this.spy(this.testModel, 'doSomething');
ok(!this.testModel.doSomething.called);
this.testModel.doSomething();
ok(this.testModel.doSomething.calledOnce);
this.testModel.set('selection','something new');
ok(this.testModel.doSomething.calledTwice); //this test should past, but fails. Console shows two "Something has been done" logs.
});
The third ok fails, even though the function was effectively called from the backbone event binding, as demo'd by the console.
This is very frustrating and has shaken my confidence on whether sinon.js is suitable for testing my backbone app. Am I doing something wrong, or is this a problem with how sinon detects whether something has been called? Is there a workaround?
EDIT: Here's a solution to my specific example, based on the monkey patch method of the accepted answer. While its a few lines of extra setup code in the test itself, (I don't need the module function any more) it gets the job done. Thanks, mu is too short
test("intra-model event bindings", function() {
var that = this;
var init = TestModel.prototype.initialize;
TestModel.prototype.initialize = function() {
that.spy(this, 'doSomething');
init.call(this);
};
this.testModel = new TestModel();
. . . // tests pass!
});
Calling this.spy(this.testModel, 'doSomething') replaces the testModel.doSomething method with a new wrapper method:
var spy = sinon.spy(object, "method");
Creates a spy for object.method and replaces the original method with the spy.
So this.spy(this.testModel, 'doSomething') is effectively doing something like this:
var m = this.testModel.doSomething;
this.testModel.doSomething = function() {
// Spying stuff goes here...
return m.apply(this, arguments);
};
This means that testModel.doSomething is a different function when you bind the event handler in initialize:
this.bind('change:selection', this.doSomething);
than it is after you've attached your spying. The Backbone event dispatcher will call the original doSomething method but that one doesn't have the Sinon instrumentation. When you call doSomething manually, you're calling the new function that spy added and that one does have the Sinon instrumentation.
If you want to use Sinon to test your Backbone events, then you'll have to arrange to have the Sinon spy call applied to the model before you bind any event handlers and that probably means hooking into initialize.
Maybe you could monkey-patch your model's initialize to add the necessary spy calls before it binds any event handlers:
var init = Model.prototype.initialize;
Model.prototype.initialize = function() {
// Set up the Spy stuff...
init.apply(this, arguments);
};
Demo: http://jsfiddle.net/ambiguous/C4fnX/1/
You could also try subclassing your model with something like:
var Model = Backbone.Model.extend({});
var TestModel = Model.extend({
initialize: function() {
// Set up the Spy stuff...
Model.prototype.initialize.apply(this, arguments);
}
});
And then use TestModel instead of Model, this would give you an instrumented version of Model in TestModel without having to include a bunch of test-specific code inside your normal production-ready Model. The downside is that anything else that uses Model would need to be subclassed/patched/... to use TestModel instead.
Demo: http://jsfiddle.net/ambiguous/yH3FE/1/
You might be able to get around the TestModel problem with:
var OriginalModel = Model;
Model = Model.extend({
initialize: function() {
// Set up the Spy stuff...
OriginalModel.prototype.initialize.apply(this, arguments);
}
});
but you'd have to get the ordering right to make sure that everyone used the new Model rather than the old one.
Demo: http://jsfiddle.net/ambiguous/u3vgF/1/

mocking window.location.href in Javascript

I have some unit tests for a function that makes use of the window.location.href -- not ideal I would far rather have passed this in but its not possible in the implementation. I'm just wondering if its possible to mock this value without actually causing my test runner page to actually go to the URL.
window.location.href = "http://www.website.com?varName=foo";
expect(actions.paramToVar(test_Data)).toEqual("bar");
I'm using jasmine for my unit testing framework.
The best way to do this is to create a helper function somewhere and then mock that:
var mynamespace = mynamespace || {};
mynamespace.util = (function() {
function getWindowLocationHRef() {
return window.location.href;
}
return {
getWindowLocationHRef: getWindowLocationHRef
}
})();
Now instead of using window.location.href directly in your code simply use this instead. Then you can replace this method whenever you need to return a mocked value:
mynamespace.util.getWindowLocationHRef = function() {
return "http://mockhost/mockingpath"
};
If you want a specific part of the window location such as a query string parameter then create helper methods for that too and keep the parsing out of your main code. Some frameworks such as jasmine have test spies that can not only mock the function to return desired values, but can also verified it was called:
spyOn(mynamespace.util, 'getQueryStringParameterByName').andReturn("desc");
//...
expect(mynamespace.util.getQueryStringParameterByName).toHaveBeenCalledWith("sort");
I would propose two solutions which have already been hinted at in previous posts here:
Create a function around the access, use that in your production code, and stub this with Jasmine in your tests:
var actions = {
getCurrentURL: function () {
return window.location.href;
},
paramToVar: function (testData) {
...
var url = getCurrentURL();
...
}
};
// Test
var urlSpy = spyOn(actions, "getCurrentURL").andReturn("http://my/fake?param");
expect(actions.paramToVar(test_Data)).toEqual("bar");
Use a dependency injection and inject a fake in your test:
var _actions = function (window) {
return {
paramToVar: function (testData) {
...
var url = window.location.href;
...
}
};
};
var actions = _actions(window);
// Test
var fakeWindow = {
location: { href: "http://my/fake?param" }
};
var fakeActions = _actions(fakeWindow);
expect(fakeActions.paramToVar(test_Data)).toEqual("bar");
You need to simulate local context and create your own version of window and window.location objects
var localContext = {
"window":{
location:{
href: "http://www.website.com?varName=foo"
}
}
}
// simulated context
with(localContext){
console.log(window.location.href);
// http://www.website.com?varName=foo
}
//actual context
console.log(window.location.href);
// http://www.actual.page.url/...
If you use with then all variables (including window!) will firstly be looked from the context object and if not present then from the actual context.
Sometimes you may have a library that modifies window.location and you want to allow for it to function normally but also be tested. If this is the case, you can use a closure to pass your desired reference to your library such as this.
/* in mylib.js */
(function(view){
view.location.href = "foo";
}(self || window));
Then in your test, before including your library, you can redefine self globally, and the library will use the mock self as the view.
var self = {
location: { href: location.href }
};
In your library, you can also do something like the following, so you may redefine self at any point in the test:
/* in mylib.js */
var mylib = (function(href) {
function go ( href ) {
var view = self || window;
view.location.href = href;
}
return {go: go}
}());
In most if not all modern browsers, self is already a reference to window by default. In platforms that implement the Worker API, within a Worker self is a reference to the global scope. In node.js both self and window are not defined, so if you want you can also do this:
self || window || global
This may change if node.js really does implement the Worker API.
Below is the approach I have take to mock window.location.href and/or anything else which maybe on a global object.
First, rather than accessing it directly, encapsulate it in a module where the object is kept with a getter and setter. Below is my example. I am using require, but that is not necessary here.
define(["exports"], function(exports){
var win = window;
exports.getWindow = function(){
return win;
};
exports.setWindow = function(x){
win = x;
}
});
Now, where you have normally done in your code something like window.location.href, now you would do something like:
var window = global_window.getWindow();
var hrefString = window.location.href;
Finally the setup is complete and you can test your code by replacing the window object with a fake object you want to be in its place instead.
fakeWindow = {
location: {
href: "http://google.com?x=y"
}
}
w = require("helpers/global_window");
w.setWindow(fakeWindow);
This would change the win variable in the window module. It was originally set to the global window object, but it is not set to the fake window object you put in. So now after you replaced it, the code will get your fake window object and its fake href you had put it.
This works for me:
delete window.location;
window.location = Object.create(window);
window.location.href = 'my-url';
This is similar to cpimhoff's suggestion, but it uses dependency injection in Angular instead. I figured I would add this in case someone else comes here looking for an Angular solution.
In the module, probably the app.module add a window provider like this:
#NgModule({
...
providers: [
{
provide: Window,
useValue: window,
},
],
...
})
Then in your component that makes use of window, inject window in the constructor.
constructor(private window: Window)
Now instead of using window directly, use the component property when making use of window.
this.window.location.href = url
With that in place you can set the provider in Jasmine tests using TestBed.
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
{
provide: Window,
useValue: {location: {href: ''}},
},
],
}).compileComponents();
});
IMO, this solution is a small improvement of cburgmer's in that it allows you to replace window.location.href with $window.location.href in the source. Granted I'm using Karma and not Jasmine, but I believe this approach would work with either. And I've added a dependency on sinon.
First a service / singleton:
function setHref(theHref) {
window.location.href = theHref;
}
function getHref(theHref) {
return window.location.href;
}
var $$window = {
location: {
setHref: setHref,
getHref: getHref,
get href() {
return this.getHref();
},
set href(v) {
this.setHref(v);
}
}
};
function windowInjectable() { return $$window; }
Now I can set location.href in code by injecting windowInjectable() as $window like this:
function($window) {
$window.location.href = "http://www.website.com?varName=foo";
}
and mocking it out in a unit test it looks like:
sinon.stub($window.location, 'setHref'); // this prevents the true window.location.href from being hit.
expect($window.location.setHref.args[0][0]).to.contain('varName=foo');
$window.location.setHref.restore();
The getter / setter syntax goes back to IE 9, and is otherwise widely supported according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set
Here's my generic solution that requires an extra import in production code, but doesn't require dependency injection or writing individual wrapper functions like getHref().
Basically we toss the window into a separate file and then our prod code imports the window indirectly from that file.
In production, windowProxy === window.
In tests we can mutate the module which exports windowProxy and mock it with a new temporary value.
// windowProxy.js
/*
* This file exists solely as proxied reference to the window object
* so you can mock the window object during unit tests.
*/
export default window;
// prod/someCode.js
import windowProxy from 'path/to/windowProxy.js';
export function changeUrl() {
windowProxy.location.href = 'https://coolsite.com';
}
// tests/someCode.spec.js
import { changeUrl } from '../prod/someCode.js';
import * as windowProxy from '../prod/path/to/windowProxy.js';
describe('changeUrl', () => {
let mockWindow;
beforeEach(() => {
mockWindow = {};
windowProxy.default = myMockWindow;
});
afterEach(() => {
windowProxy.default = window;
});
it('changes the url', () => {
changeUrl();
expect(mockWindow.location.href).toEqual('https://coolsite.com');
});
});
You need to fake window.location.href while being on the same page.
In my case, this snipped worked perfectly:
$window.history.push(null, null, 'http://server/#/YOUR_ROUTE');
$location.$$absUrl = $window.location.href;
$location.replace();
// now, $location.path() will return YOUR_ROUTE even if there's no such route

Categories

Resources