Stubbing window.location.href with Sinon - javascript

I am trying to test some client-side code and for that I need to stub the value of window.location.href property using Mocha/Sinon.
What I have tried so far (using this example):
describe('Logger', () => {
it('should compose a Log', () => {
var stub = sinon.stub(window.location, 'href', 'http://www.foo.com');
});
});
The runner displays the following error:
TypeError: Custom stub should be a function or a property descriptor
Changing the test code to:
describe('Logger', () => {
it('should compose a Log', () => {
var stub = sinon.stub(window.location, 'href', {
value: 'foo'
});
});
});
Which yields this error:
TypeError: Attempted to wrap string property href as function
Passing a function as third argument to sinon.stub doesn't work either.
Is there a way to provide a fake window.location.href string, also avoiding redirection (since I'm testing in the browser)?

You need to use global to mock the window object for your test in beforeEach or it
e.g.
it('should compose a Log', () => {
global.window = {
location: {
href: {
value: 'foo'
}
}
}
//.... call the funciton
});

Stubs cannot replace attributes, only functions.
The error thrown reinforces this:
TypeError: Custom stub should be a function or a property descriptor
From the documentation:
When to use stubs?
Use a stub when you want to:
Control a method’s behavior from a test to force the code down a specific path. Examples include forcing a method to throw an error in order to test error handling.
When you want to prevent a specific method from being called directly (possibly because it triggers undesired behavior, such as a XMLHttpRequest or similar).
http://sinonjs.org/releases/v2.0.0/stubs/
Possible solution
While many builtin objects can be replaced (for testing) some can't. For those attributes you could create facade objects which you then have to use in your code and being able to replace them in tests.
For example:
var loc = {
setLocationHref: function(newHref) {
window.location.href = newHref;
},
getLocationHref: function() {
return window.location.href;
}
};
Usage:
loc.setLocationHref('http://acme.com');
You can then in your test write
var stub = sinon.stub(loc, 'setLocationHref').returns('http://www.foo.com');
Note the chained returns() call. There was another error in your code: the third argument has to be a function, not value on another type. It's a callback, not what the attribute should return.
See the source code of stub()

Use window.location.assign(url) instead of overwriting the value of window.location. Then you can just stub the assign method on the window.location object.
http://www.w3schools.com/jsref/met_loc_assign.asp
UPDATE: I tested this in a headless browser, but it may not work if you run your tests in Chrome. See #try-catch-finally's response.

Related

Protractor is not able to find a function from JS file

I created a function which return the new Browser object from the JS function browser.forkNewDriverInstance() and i created a global variable in my config file and i'm calling a function from that file by using this global variable. but here when i'm calling that function it is throwing error like utility.openNewBrowser is not a function error.
Config File:
onPrepare: function () {
global.utility=require("../src/test/resources/com.learnFramework.utility/timeOutConfig.js");
}
Cucumber Opts functions
cucumberOpts: {
//i'm using the same file for setting up the timeout.. is this creating the issue??
require:['../src/test/resources/com.learnFramework.utility/timeOutConfig.js'],
tags: false,
profile: false,
format:'json:../Reports/jsonResult/results.json',
'no-source': true
}
Function
var configure = function () {
this.setDefaultTimeout(100 * 1000);
this.openNewBrowser=function(){
return browser.forkNewDriverInstance(true);
}
};
module.exports = configure;
Error Log
TypeError: utility.openNewBrowser is not a function
When i called the forkNewBrowserInstance method directly i'm getting the below error.
both angularJS testability and angular testability are undefined. This could be either because this is a non-angular page or because your test involves client-side navigation, which can interfere with Protractor's bootstrapping. See http://git.io/v4gXM for details
can some help me to resolve this issue.. i got this error because the first browser ignoring synchronization but second browser how can i ignore the synchronization?
What you did above is the correct way of defining a global variable. But I think the global variable name should be configure instead of utility that is why it is throwing the TypeError.
And wherever you want to call it, use that variable name as is. This is actually how protractor, browser and other built-in global variables were made available globally. The following posting was helpful and also the protractor doc where it's explaining the property: params?: any;
Hope this helps, please let me know.
I faced the same issue but with jasmine.
so i had done the following workaround and issue was resolved.
class abc{
constructor() {
var configure = function () {
this.setDefaultTimeout(100 * 1000);
this.openNewBrowser=function(){
return browser.forkNewDriverInstance(true);
}
};
}
}
module.exports = new abc();

Additional url attribute at nightwatch page object

I was trying to add an additional url attribute as a function to my page-object while using nightwatchjs.
Like:
module.exports = {
url: function() {
return this.api.launchUrl + '/content/site1.xhtml';
},
cancelUrl: function() {
return this.api.launchUrl + '/content/cancel_site1.xhtml';
}
}
Anyhow nightwatch is not able to get that 2nd attribute cancelUrl, ie undefined.
Why is that so? Shouldn't nightwatch be able to access that attribute as it is nothing more than a function call returning a string or am I misunderstanding a javascript or special page-object concept?
--
I am aware that there should be a page-object for each site so there should not be a 2nd site. Anyhow I would like to understand why this is not working technically.
Not sure I can answer the "why" (other than to say that when nightwatch loads up your page objects as globally available it must be wrapping your js file and filtering on 'known' functions) but I can offer a solution: add a command to your page object with the desired function. For example:
let pageCommands = {
cancelUrl: function() {
return this.api.launchUrl + '/content/cancel_site1.xhtml';
}
};
module.exports = {
commands: [pageCommands],
...
}
It's not the typical use of page commands, but your test would then be able to access the cancelUrl function on the page object instance.
More on page commands here

Protractor testing, access and modify Window object properties

I'm trying to write a simple e2e test for the authentication we use in our project, Authentication is based on a json web token which is set into window.localStorage.satellizer_token .
To set it i use the code below, but for what i see it doesn't really set the real localStorage property of the window object.
describe('login', function () {
it('should set the satellizer token and be allowed to get panel', function () {
browser.driver.get('http://example.com/');
browser.driver.executeScript(function () {
return window.localStorage;
}).then(function (localStorage) {
expect(localStorage.satellizer_token).toBe(undefined);
localStorage.satellizer_token = "eyJ0fdaccKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjE3MjUzIiwiaWF0IjoxNDM0Mzc1NjU3LCJleHAiOjE0NjU5Mjk2NTd9.VbhdWQ_gOb7X8pmOGLDjBKURxcaWQlIXQGvLRansQCphk";
expect(localStorage.satellizer_token).toBe("eyJ0fdaccKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjE3MjUzIiwiaWF0IjoxNDM0Mzc1NjU3LCJleHAiOjE0NjU5Mjk2NTd9.VbhdWQ_gOb7X8pmOGLDjBKURxcaWQlIXQGvLRansQCphk");
browser.driver.get('http://example.com/panel');
expect(browser.driver.getTitle()).toEqual('http://example.com/panel');
expect(browser.driver.getCurrentUrl()).toEqual('http://example.com/panel');
});
});
});
I know there is already something similar here and here but all the examples i can find are about access-only, i need also to modify window properties.
What is the correct way to interact with the window object in protractor tests?
Working solution:
browser.executeScript(function () {
window.localStorage.satellizer_token = "eyJ0eXAiOiJKV1QiLCJhbGasdsOiJIUzI1NiJ9.eyJpZCI6IjE3MjUzIiwiaWF0IjoxNDM0Mzc1NjU3LCJleHAiOjE0NjU5Mjk2NTd9.VbhdWQ_gOb7X8pmOGLDjBKURQUQlcAfGSGvLRansQCphk";
});
browser.driver.manage().window()
Seen here: https://github.com/bolshchikov-public/protractor-best-practices/blob/master/Practices.md#set-screen-size

How to spyOn a value property (rather than a method) with Jasmine

Jasmine's spyOn is good to change a method's behavior, but is there any way to change a value property (rather than a method) for an object? the code could be like below:
spyOn(myObj, 'valueA').andReturn(1);
expect(myObj.valueA).toBe(1);
In February 2017, they merged a PR adding this feature, they released in April 2017.
so to spy on getters/setters you use:
const spy = spyOnProperty(myObj, 'myGetterName', 'get');
where myObj is your instance, 'myGetterName' is the name of that one defined in your class as get myGetterName() {} and the third param is the type get or set.
You can use the same assertions that you already use with the spies created with spyOn.
So you can for example:
const spy = spyOnProperty(myObj, 'myGetterName', 'get'); // to stub and return nothing. Just spy and stub.
const spy = spyOnProperty(myObj, 'myGetterName', 'get').and.returnValue(1); // to stub and return 1 or any value as needed.
const spy = spyOnProperty(myObj, 'myGetterName', 'get').and.callThrough(); // Call the real thing.
Here's the line in the github source code where this method is available if you are interested.
https://github.com/jasmine/jasmine/blob/7f8f2b5e7a7af70d7f6b629331eb6fe0a7cb9279/src/core/requireInterface.js#L199
And the spyOnProperty method is here
Answering the original question, with jasmine 2.6.1, you would:
const spy = spyOnProperty(myObj, 'valueA', 'get').andReturn(1);
expect(myObj.valueA).toBe(1);
expect(spy).toHaveBeenCalled();
Any reason you cannot just change it on the object directly? It is not as if javascript enforces visibility of a property on an object.
The best way is to use spyOnProperty. It expects 3 parameters and you need to pass get or set as a third param.
Example
const div = fixture.debugElement.query(By.css('.ellipsis-overflow'));
// now mock properties
spyOnProperty(div.nativeElement, 'clientWidth', 'get').and.returnValue(1400);
spyOnProperty(div.nativeElement, 'scrollWidth', 'get').and.returnValue(2400);
Here I am setting the get of clientWidth of div.nativeElement object.
Jasmine doesn't have that functionality, but you might be able to hack something together using Object.defineProperty.
You could refactor your code to use a getter function, then spy on the getter.
spyOn(myObj, 'getValueA').andReturn(1);
expect(myObj.getValueA()).toBe(1);
The right way to do this is with the spy on property, it will allow you to simulate a property on an object with an specific value.
const spy = spyOnProperty(myObj, 'valueA').and.returnValue(1);
expect(myObj.valueA).toBe(1);
expect(spy).toHaveBeenCalled();
If you are using ES6 (Babel) or TypeScript you can stub out the property using get and set accessors
export class SomeClassStub {
getValueA = jasmine.createSpy('getValueA');
setValueA = jasmine.createSpy('setValueA');
get valueA() { return this.getValueA(); }
set valueA(value) { this.setValueA(value); }
}
Then in your test you can check that the property is set with:
stub.valueA = 'foo';
expect(stub.setValueA).toHaveBeenCalledWith('foo');
Suppose there is a method like this that needs testing
The src property of the tiny image needs checking
function reportABCEvent(cat, type, val) {
var i1 = new Image(1, 1);
var link = getABC('creosote');
link += "&category=" + String(cat);
link += "&event_type=" + String(type);
link += "&event_value=" + String(val);
i1.src = link;
}
The spyOn() below causes the "new Image" to be fed the fake code from the test
the spyOn code returns an object that only has a src property
As the variable "hook" is scoped to be visible in the fake code in the SpyOn and also later after the "reportABCEvent" is called
describe("Alphabetic.ads", function() {
it("ABC events create an image request", function() {
var hook={};
spyOn(window, 'Image').andCallFake( function(x,y) {
hook={ src: {} }
return hook;
}
);
reportABCEvent('testa', 'testb', 'testc');
expect(hook.src).
toEqual('[zubzub]&arg1=testa&arg2=testb&event_value=testc');
});
This is for jasmine 1.3 but might work on 2.0 if the "andCallFake" is altered to
the 2.0 name
I'm using a kendo grid and therefore can't change the implementation to a getter method but I want to test around this (mocking the grid) and not test the grid itself. I was using a spy object but this doesn't support property mocking so I do this:
this.$scope.ticketsGrid = {
showColumn: jasmine.createSpy('showColumn'),
hideColumn: jasmine.createSpy('hideColumn'),
select: jasmine.createSpy('select'),
dataItem: jasmine.createSpy('dataItem'),
_data: []
}
It's a bit long winded but it works a treat
You can also use
jasmin.creatSpyObj('ObjectName', [methodNames...], {prop1:propvalue, prop2:provalue2})
I'm a bit late to the party here i know but,
You could directly access the calls object, which can give you the variables for each call
expect(spy.calls.argsFor(0)[0].value).toBe(expectedValue)
You can not mock variable but you can create getter function for it and mock that method in your spec file.

How to test the expectation on the eventSpy

I am trying to test a backbone.model when saving.
Here's my piece of code.
As you can see from the comment there is a problem with toHaveBeenCalledOnce method.
P.S.:
I am using jasmine 1.2.0 and Sinon.JS 1.3.4
describe('when saving', function ()
{
beforeEach(function () {
this.server = sinon.fakeServer.create();
this.responseBody = '{"id":3,"title":"Hello","tags":["garden","weekend"]}';
this.server.respondWith(
'POST',
Routing.generate(this.apiName),
[
200, {'Content-Type': 'application/json'}, this.responseBody
]
);
this.eventSpy = sinon.spy();
});
afterEach(function() {
this.server.restore();
});
it('should not save when title is empty', function() {
this.model.bind('error', this.eventSpy);
this.model.save({'title': ''});
expect(this.eventSpy).toHaveBeenCalledOnce(); // TypeError: Object [object Object] has no method 'toHaveBeenCalledOnce'
expect(this.eventSpy).toHaveBeenCalledWith(this.model, 'cannot have an empty title');
});
});
console.log(expect(this.eventSpy));
Jasmine has no function toHaveBeenCalledOnce. You need to check the count yourself.
expect(this.eventSpy).toHaveBeenCalled();
expect(this.eventSpy.callCount).toBe(1);
So I guess in your case, you'd want this:
expect(this.eventSpy.callCount).toBe(1);
expect(this.eventSpy).toHaveBeenCalledWith(this.model, 'cannot have an empty title');
Updated
The error you are getting now, "Expected a spy, but got Function" is because of exactly that. You are using a Sinon library Spy, and passing it to a Jasmine function that expects a Jasmine Spy.
You should do either:
this.eventSpy = jasmine.createSpy();
or
expect(this.eventSpy.calledOnce).toBe(true);
expect(this.eventSpt.calledWith(this.model, 'cannot have an empty title')).toBe(true);
What was your reasoning behind using Sinon along with Jasmine? I'd recommend the first solution, since then Jasmine will have more info to show when the test fails.
There's a library called jasmine-sinon that adds sinon-specific matchers to jasmine.
It allows you to do things like
expect(mySpy).toHaveBeenCalledOnce();
expect(mySpy).toHaveBeenCalledBefore(myOtherSpy);
expect(mySpy).toHaveBeenCalledWith('arg1', 'arg2', 'arg3');
Try with the toHaveBeenCalledTimes method:
expect(this.eventSpy).toHaveBeenCalledTimes(1);

Categories