I found that many people answered peoples' questions on this subject with irrelevant answers, like how to Mock an AJAX request. Usually on StackOverflow.
Quite personally, I hate Mocking as it doesn't really serve too much of a purpose if you are developing both ends as the Mock has to change if the server request/response changes. This is the case where the Mock just mimics the specification of the code. Useless in this sense, because you spend too much time specifying the test just to mimic the code. If you have to change the test every time you change the code, you're doing the wrong thing. I'm not a big fan of test driven development, but these days it's the only way to figure out how things DON'T work. That is because nobody took the time to implement correctly, THINK about the edge cases, and fully explore the problem Which is something they SHOULD have taught in school these days. I apologize, I digress. Let's get to the problem at hand.
For my particular problem I wanted to see if an object called API could get to the server and load some XML from the response.
get : function(cb) {
var http = new XMLHttpRequest();
http.onreadystatechange = this._onAPIResponse(http, cb);
http.open("GET", this.hostUrl + "apis/get_api.xml", true);
http.send();
},
_onAPIResponse : function(http, cb) {
var callback = function (event) {
if (http.readyState == 4) {
if (http.status == 200) {
var apiXML = http.responseXML;
this._loadAPIMap(apiXML);
cb(this,true);
} else {
cb(this,false);
};
};
};
return callback.bind(this);
},
You can get a Jasmine spec to issue an AJAX request and get to the server okay. However, on the client side, the response gets lost somewhere. So, therefore, testing Async Javascript calls in Jasmine is pretty useless when using XMLHttpRequest.This is what happens. The following contains a Jasmine spec for testing the API.get() function to see if it can get to the server. We use a real callback function and use the Jasmine runs() and waitsFor() functions to handle the asynchronous nature of the calls in Jasmine.
it('should be able to login', function () {
var called = false;
var callback = function (api, result) {
called = true;
};
runs( function() {
api.get(callback);
});
waitsFor(function() {
return called;
});
runs( function () {
expect(api.isLoaded()).toEqual(true);
});
});
The following is supposed to happen is when the AJAX call is made. If the request finishes (readyState == 4) returns a OK status (status == 200) it loads the XML and the API.isLoaded() function returns true. The callback function is there for notification.
Using the Jasmine test above, with a couple of clever alerts or console logs, this is what I see happens.
1. The Jasmine spec calls api.get().
2. api.get() makes the AJAX request.
3. The server gets the request.
4. The server responds with the proper XML and returns a status of 200 OK.
5. The callback gets called with (readyState == 2, status == 0, responseXML == null).
6. The callback gets called again with (readyState == 4, status == 0, responseXML == null).
7. Polar goes WTF?
Thinking things were wrong with cookies, rails protect_from_forgery, and such, I researched and eliminated all causes, even with a peek into the stream with Wireshark.
Now, in order to confirm for my own sanity that the AJAX request was actually working, I set up a small HTML file for it to respond with a script containing:
function callback(api, result) {
alert("Result " + result);
};
var api = new BusPassAPI("http://localhost:3000/");
api.get(callback);
I definitely can say that the AJAX call works and gets the proper answer and loads the XML correctly.
So, there is something a miss in Jasmine. So, what I hoped to accomplish seems like it needs yet another framework to test. I neither have the time nor inclination to find out what is wrong here.
The problem is that I want to test the integration of the Javascript with the server, because that's where it is going to break. Not in the Javascript programming and testing to see if I can actually program correctly. However, there is always a mismatch of what might happen if two separate entities don't play with each other nicely. So, I'm more interested in the "integration testing".
Unfortunately, the frameworks that work with Rails seem like an horrendously tedious amount of stuff about manipulating web pages, specifying clicks, and such, when all I want to do is see how a particular function work with the server at hand. I haven't found that answer yet.