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.
Related
I am using sinon to make my tests, and I faced a problem that I can't find a proper solution
lets say, for simplicity, that I want, when I call console.log(),that the proper console.log receives the argument that I want in my test.
I have tryied:
Sinon.replace( console, 'log', () => console.log('mytestparam'))
Sinon.stub( console, 'log').callsFake( console.log('mytestparam'))
But it creates some kind of circular dependency that crashes
I cant simply make a full stub of the function, because it contains code that I want to run, I only want to replace the argument used for the call.
I have researched the sinon docs but I can't find any function that allows to call the original function changing the args
Any idea?
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?
I want to do something like the following:
sinon.mock(obj)
.expects('func')
.atLeast(1)
.withArgs(args)
.returns(somePredefinedReturnValue);
Where I expect everything up to and including withArgs, but then I need to stub the return value of the method so that when it returns it doesn't break the rest of the execution flow within the method under test.
The reason I'm doing this is because I found out that some of my REST endpoint tests will silently pass when they should really be failing if a stubbed method with a callback that has an assertion inside of it doesn't get called. I'm trying to verify that these callbacks are actually getting called so that my tests don't give false positives.
In the official docs
http://sinonjs.org/docs/#stubs
var stub = sinon.stub(object, "method", func);
You could pass a function argument that returns your desired value.
EDIT:
This has been removed from v3.0.0. Instead you should use
stub(obj, 'meth').callsFake(fn)
Why following piece of code doesn't work since jQuery 1.9.1? With previous versions works fine.
$(function () {
$(document).append(test);
document.write('done');
});
var test = {
version: "1.0",
};
JSFiddle: http://jsfiddle.net/Chessjan/NsjqM/
In JS console it issues error like this:
TypeError: document is null
safeFrag = document.createDocumentFragment(); jquery-1.9.1.js (line 5823)
Edit:
Thanks everybody for quick and extensive aswers. Observed issue was found by accident, and of course, $(document.body).append() is proper approach.
jQuery 1.9.x calls
this[ 0 ].ownerDocument
within its buildFragment() method. Since you pass in the document, the call
document.ownerDocument
will reference to null and cause the error. Any other node will reference the document, which of course, works.
Conclusion: Don't call $(document).append() but use $(document.body) for instance.
Your code will of never worked. It has to document.body not document.
Here's a few examples in different versions of it not working:
jQuery 1.6.4: http://jsfiddle.net/us9Kz/
jQuery 1.7.2: http://jsfiddle.net/us9Kz/1/
jQuery 1.8.3: http://jsfiddle.net/us9Kz/3/
jQuery 1.9.1: http://jsfiddle.net/us9Kz/4/
jQuery 2.0.0b1: http://jsfiddle.net/us9Kz/5/
Code working with document.body (on jQuery 1.9.1): http://jsfiddle.net/us9Kz/6/
Inside the jQuery code it has this line:
jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
this is the jQuery object you selected. In your case, the document. The ownerDocument value of document is null and this is what is passed through as document to the call to document.createDocumentFragment();. Hence you get the error that document is null (Slightly bad naming of variables there as it makes you think the document object itself is somehow null)
As other people have said. Append to the body instead and it will work fine.
To answer your question i tried in JSfiddle all the available jQuery versions. It happened to give the same error.
Why it doesnt work: document becomes something like [object HTMLDocument] when cast to string, and there is of course no such id, it will return null.
The following works:
var test = "1.0"
$('body').append(test);
or doing it trough object notation like you did:
var test = {
version: '1.0'
}
$('body').append(test.version)
I'm using a jQuery plugin called toggleEdit for inline editing.
Everything works fine when the code is actually used in the page.
However, my test suite fails with the following error:
TypeError: Cannot call method 'remove' of undefined
I tracked it down to be triggered from within the clear method of this particular plugin. Its source file can be found here.
There are two relevant bits in that code:
1- The _init function
self.element.addClass("toggleEdit toggleEdit-edit toggleEdit-edit-" +
self._tag(self.element))
//store reference to preview element
.data("toggleEdit-preview", self.p);
As you can see, when the plugin is first instantiated it uses the data structure on self to store the newly created element.
2- The clear function
self.element.data("toggleEdit-preview").remove();
The clear function then tries to access that structure and retrieve the element. That's when, while inside a jasmine spec, it fails with the aforementioned exception.
Has anyone seen anything similar?
EDIT:
This is my spec, it's the simplest piece of code able to reproduce the error:
it("should update the given attribute on the server", function(){
$('#user-details input, #user-details select').toggleEdit(); //this line triggers the error
});
http://alz.so/static/plugins/toggleedit/jquery.toggleedit.js
I was taking a look at the source for toggleEdit and it seems that the only 2 times the function clear is called is just before self.element.data gets set:
if (typeof self.element.data("toggleEdit-preview") !== "undefined") {
self.clear();
self.disableEvents();
}
And at destroy function:
destroy: function() {
var self = this;
self.clear();
self.disableEvents();
$.Widget.prototype.destroy.apply(self, arguments);
}
Since the first call seems to be protected, I ask you a somewhat dumb question: Is it possible that destroy is being called twice?
Found my problem: old version of the jQuery + jQuery UI duo. Upgrading them resolves the exception.