Stubbing WebSocket in JavaScript with Jasmine
jasmine, javascript, stub, testing, websocket
Solution
I'm not completely sure that I figured out how your code is supposed to behave, but if you need to spy on `WebSocket` constructor and stub `.send` method to mock some incoming messages, here is how to achieve it.
To spy on `WebSocket` you will need to call `.and.callThrough` rather than `returnValue`. However it result with complains about lack of `new` keyword (as mentioned in here), so you need to fake the constructor:
var realWS = WebSocket;
var WebSocketSpy = spyOn(window, "WebSocket").and.callFake(function(url,protocols){
return new realWS(url,protocols);
});
To make a spy for incoming messages you can simply do
var onmessageCallbackSpy = jasmine.createSpy('onmessageCallback');
You can also spy on `.send` method, and provide some mocked responses with:
var sendSpy = spyOn(WebSocket.prototype, "send").and.callFake(function(outMsg){
// process/check outgoing message
// setTimeout, or call it immediately
this.onmessage("mock message goes here");
});
But make sure to use `WebSocket.prototype`, before you replace it with `WebSocketSpy`.
So full working example, should look like this:
it("should spy and callFake WebSocket constructor, and stub prototype methods", function (done) {
var realWS= WebSocket;
var sendSpy = spyOn(WebSocket.prototype, "send").and.callFake(function(outMsg){
if(outMsg == "outgoing message"){
this.onmessage("incoming mocked message goes here");
}
});
// var messageSpy = spyOn(WebSocket.prototype, "onmessage");//.and.returnValue("mock message goes here");
var WSSpy = spyOn(window, "WebSocket").and.callFake(function(url,protocols){
return new realWS(url,protocols);
});
var onmessageCallbackSpy = jasmine.createSpy('onmessageCallback');
// Your code
// (function init(url, onmessageCallbackSpy){
var ws = new WebSocket("ws://some/where");
ws.onmessage = onmessageCallbackSpy;
// code that results with receiving a message
// or mocked send, that calls `.onmessage` immediately
ws.send("outgoing message");
// })();
expect(WSSpy).toHaveBeenCalledWith("ws://some/where");
expect(onmessageCallbackSpy).toHaveBeenCalledWith("mock message goes here");
done();
});
Problem
I try to test if `onmessage` is a proper function. Here is a test: ``` describe(".init(address, window)", function() { beforeEach(function() { address = 'ws://test.address'; window = {}; e = { data: {} } spyOn(window, 'WebSocket').and.returnValue(function() {return {onmessage: null}}); spyOn(subject, 'handleMessage'); }); it("should create a WebSocket client which connects to the given address", function() { subject.init(address, window); expect(window.WebSocket).toHaveBeenCalledWith(address); }); it("should have onmessage method overriden with a function which handles message", function() { ws = subject.init(address, window); alert(JSON.stringify(ws)); ws.onmessage(e); expect(subject.handleMessage).toHaveBeenCalledWith(e.data); }); }); ``` Here is the implementation: ``` FL.init = function(address, window) { if ('WebSocket' in window) { var ws = new WebSocket(address); ws.onmessage = function(e) { this.handleMessage(e.data); }; return ws; } }; ``` The first test passes. In the second, `ws` is `undefined`. Why is that? I tried in a console `new function() {return {onmessage: null}}` and it looks it should be ok.