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.

Original source