Jasmine Unit Tests Unexpected this Reference - javascript

I am trying to test a fairly simple JavaScript function in Jasmine, however the first statement is throwing an error for being undefined.
myClass.prototype.functiontoBeTested = function() {
var x = this.method()
...
}
The above throws an error in Jasmine as method is not a function and is undefined. The prototype is altered earlier to have this method, and out of curiosity I assigned this method to my test object in the spec itself as such:
myObject.method = function(){return mockResults;};
So I decided to log this to the console and instead of it being my object, I see Window {top: Window, location: Location, document: document, window: Window, external: Object…} which doesn't seem right. I've had this issue before with testing a function in Jasmine that used this but just changed the source code to refer to the object by name since the function was being assigned to something within the closure. I can't do that in this case, and I'm curious why this is referring to something unexpected (at least to me).
Edit: Some details on what the test case looks like as requested:
it("updates the control count", function(){
var mockResults = { ... };
myObject.method = function() {return mockResults;};
expect(myObject.method).not.toThrow();
});
Right now I'm just trying to get the method to execute to completion during the test. The function to be tested updates the text on some HTML components, I'll work on verifying those once I can get it to actually run. The method that is causing an error is the first line of the function, and is simply an accessor method for the object being called. In actual execution, var x = this.method() runs without issue. When testing in jasmine var x = this.method() throws an error because method() is undefined for this. Instead of this referring to the calling object, it is referring to the window. This doesn't happen live, but only during testing with Jasmine. This method is undefined even when I forcibly define it for the test object just prior to execution in the test as above. That's when I decided to log this to console in the source code and realized it isn't referring to what I would have expected it to refer to.

In JavaScript this for a method depends on the context it was called from. When you do a call myObject.method(), then method was called from the context of myObject, therefore this is myObject.
When you pass your function to Jasmine toThrow matcher, it calls it as it was passed (see source code):
try {
actual();
} catch (e) {
// ....
}
Here, actual() is a call of your method. It has no any specific context, so by default it will be called from window context.
Solution is to explicitly bind you method to myObject like the following:
expect(myObject.method.bind(myObject)).not.toThrow();
Credits to questions, you can find more details there:
Does Jasmine's toThrow matcher require the argument to be wrapped in an anonymous function?
How to write a test which expects an Error to be thrown in Jasmine?

Related

Window object not defined in jasmine

I am trying to test a method where one of the thing that it does it lock orientation of screen. Jasmine however is throwing error in the line:
(<any>window).screen.orientation.lock('portrait') saying that undefined is not a constructer.
I even tried not using typescript types and just window.screen.msOrientationLock('landscape') and other window.screen methods but I get same error. I have the _$window_ injected in beforeEach of my tests too.
Testing if it locks is not necessary part of my test so is there some way to skip this specific line or correct this error. Thanks :)
Well it was easy. I had to inject window object and assign it to a global variable in my global beforeEach like this:
$window = _$window_;
Then, the next issue was that the property orientation was not available in the window.screen object unfortunately. I just had to mock it inside my spec like this:
$window.screen.orientation = {
lock: function() { return; }
};
Just had to do this before spying/calling the method which had window.screen.orientation.lock method inside it.

XrayWrapper error (value is callable) when overwriting prototype method in Firefox addon

I'd like to monitor access to certain methods of the browser's built-in objects from a Firefox add-on. The sample code included below largely works, except when one of the method arguments is a function. Then, I receive the following XrayWrapper error:
XrayWrapper denied access to property 0 (reason: value is callable).
See https://developer.mozilla.org/en-US/docs/Xray_vision for more
information. Note that only the first denied property access from a
given global object will be reported.
I don't understand why an XrayWrapper error would throw, as the prototype instrumented is an object in the unsafeWindow (page-script) scope. The content-script used for instrumentation is:
function logCalls(object, objectName, methodName) {
var originalMethod = object[methodName];
object[methodName] = function () {
console.log(objectName + "." + methodName, "was called");
originalMethod.apply(this, arguments);
};
}
// 1. Works (expected)
logCalls(unsafeWindow.RTCPeerConnection.prototype,
"unsafeWindow.RTCPeerConnection", "createDataChannel");
// 2. Throws XrayWrapper Error (unexpected)
logCalls(unsafeWindow.RTCPeerConnection.prototype,
"unsafeWindow.RTCPeerConnection", "createOffer");
A sample page-script:
var PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection;
var connection = new PeerConnection({iceServers: []}, {optional: [{RtpDataChannels: !0}]});
// 1. Method call recorded
connection.createDataChannel("", {reliable: !1});
// 2. Method call recorded but causes XrayWrapper error
connection.createOffer(function(a) {
connection.setLocalDescription(a)
}, function(err) {})
For (1), the instrumentation works as expected. The call to createDataChannel logs to the console and succeeds in creating a dataChannel.
For (2), the instrumentation successfully logs to console. However, line 5 of the content-script (originalMethod.apply(this, arguments);) causes the XrayWrapper error above due to the argument list containing a function.
First of all, you should give any method assigned into a different security context the exportFunction treatment.
That may not be sufficient though, since the method body itself is still executed in the privileged context which means any arguments it receives will also be xray wrappers. Passing them back to the less privileged scope, especially through arcane magic like apply and the arguments object may not do what you would expect it to. Additional xray unwrapping or using rest parameters and .call instead may be necessary.
In cases like these it may be easier to eval() the call-interception logic into the target scope and only export the the logging function called by the interception-wrapper. That way there will not be any unprivileged -> privileged -> unprivileged transition for the arguments, the this or return values and only the log call will cross a security boundary.
Since eval is discouraged by addon review guidelines you also may want to consult the reviewers on that approach.

Spying on console.error() with Jasmine

I'm actually new to JavaScript as well as Jasmine. So it might be something really obvious that fixes my problem but I can't see it.
I want to check if (an already existing) JavaScript application calls console.error() while loading. I don't really see a way how to realise this with Jasmine. I've included the JavaScript file as well as the spec file in the SpecRunner.html.
But I take it that I somehow need to "instantiate" the application in order to test if it throws any errors on the console, right?
Or should I include the SpecRunner.html code only for this purpose into the HTML code of the app?
You can spy on console.error like this:
beforeEach(function(){
spyOn(console, 'error');
})
it('should print error to console', function(){
yourApp.start();
expect(console.error).toHaveBeenCalled();
})
You can override the standard console.error function like this:
//call the error function before it is overriden
console.error( 'foo' );
//override the error function (the immediate call function pattern is used for data hiding)
console.error = (function () {
//save a reference to the original error function.
var originalConsole = console.error;
//this is the function that will be used instead of the error function
function myError () {
alert( 'Error is called. ' );
//the arguments array contains the arguments that was used when console.error() was called
originalConsole.apply( this, arguments );
}
//return the function which will be assigned to console.error
return myError;
})();
//now the alert will be shown in addition to the normal functionality of the error function
console.error( 'bar' );
This solution works with Jasmin or anything else. Just put the code above before the other codes and any call after this to console.error() will call the overridden function.
use toThow and toThrowError http://jasmine.github.io/edge/introduction#section-Spies:_and.throwError

A weird prototype loading thing with Yabble.js

I have a very odd problem that I have to assume is because of Yabble.js. I have never used Yabble.js before, and the only reason I am now is because it is a dependency of a library I'm using (Gamejs), but I would love to understand why this happens, and whether it is actually Yabble.js's fault, or possibly Gamejs's.
Here's a heavily compressed (and modified for genericness) version of my main.js:
var gamejs = require('gamejs');
...
function Character(/*lots of arguments*/) {
Character.superConstructor.apply(this, arguments);
this.somethingtomakeitaprototypeforthisexample = oneofthearguments;
}
gamejs.utils.objects.extend(Character, gamejs.sprite.Sprite);
Character.prototype.draw = function(display){
display.blit(this.animator.image, this.pos);
}
... /*Skipping most of the file, irrelevant to the problem*/
function main() {
maincharacter = new Character(/* appropriate number and types of arguments */);
... /*skipping the rest*/
}
gamejs.ready(main);
I have done enough debugging to know that it gets into the main function no problem and that the break occurs at the call to Character. Here is the error message (from Chrome's console):
Uncaught TypeError: undefined is not a function
main
_readyResources
I have determined that Character is the undefined function. However, if I define my ready function thusly:
gamejs.ready(function(){
console.log('Character:');
console.log(Character);
main();
});
the full contents of Character, as properly defined, prints out, but I still get the error in main. Thus, I know that Character is defined by the namespace before main is called.
Fun fact though: I do have a workaround. If I change the function prototype for main to:
function main(CharacterClass) {...};
then change the ready function to:
gamejs.ready(function(){ main(Character); });
and change the relevant line in main to:
var character = new CharacterClass(...);
it works fine. But this feels really hackish.
So my question is not how to make it work, since I have that already, but rather why it is a problem and how to make it work like it's supposed to.
Any thoughts?

How to mock JQuery with Jasmine?

How can I test that a certain JQuery selector has been executed with Jasmine? I'm trying to do the following:
spyOn($.fn, 'init').andCallThrough();
// my code
expect($.init).toHaveBeenCalled();
But after this call, $('div') returns Object { selector="div", context=document, NaN=div.spec, more...}, though it has to return (and $.fn.init('div') does return it): [div.jasmine_reporter, div.banner, div.logo, 4 more...]. This stuff naturally breaks the code since the JQuery object is no longer usable.
Example:
Say I want to test that a JQuery selector has been called, I write:
it('tests', function() {
spyOn($.fn, 'init').andCallThrough();
$('html');
expect($.init).toHaveBeenCalled();
});
This result it an error from Jasmine: Error: Expected a spy, but got undefined.. Then I set a breakpoint in FireBug on $('html') line and when I get there and try to watch, what the value of $('html') is, I get:
Object { selector="html", context=document, NaN=html, more...}
If I comment out spyOn, on that line $('html') evaluates to:
[html]
Which is what I expected to see with spyOn as well.
Well it looks like Jasmine does it's spy stuff by replacing the spied-on object with a wrapped version, and that seems to be messing up jQuery as a whole because (from the jQuery source code):
// The jQuery object is actually just the init constructor 'enhanced'
I'd suggest trying to spy on one of the functions that init uses, specifically "merge". If you look at the jQuery code, you'll see that any HTML=>DOM stuff ultimately gets returned through a merge call:
return jQuery.merge( this, selector );
(that's line 152 if you happen to be looking at the source of jQuery 1.5.1).
By spying on merge you should be able to test whatever you're testing, without inadvertently replacing the guts of jQuery.

Categories

Resources