UncleDave
UncleDave

Reputation: 7188

Angular - Testing a websocket wrapper with jasmine

I'm having trouble testing my websocket wrapper: data-service is my Angular service to wrap around the native browser WebSocket implementation.

Implementation:

angular.module('core').factory('dataService', function ($interval, webSocket) {

  var sock;

  function openSocket() {
    sock = new webSocket('ws://localhost:9988');
  }

  function isReady() {
    return sock.readyState === 1;
  }

  openSocket();

  $interval(function () {
    !isReady() && openSocket();
  }, 5000);
});

webSocket is window.WebSocket extracted to an angular constant.

Test:

describe('Data Service', function () {

  var dataService,
    ws;

  jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;

  beforeEach(function () {
    module('core', function ($provide) {
      ws = jasmine.createSpy('constructor');
      $provide.constant('webSocket', ws);
    });

    inject(function (_dataService_) {
      dataService = _dataService_;
    });
  });

  it('should attempt to connect on load', function () {
    expect(ws).toHaveBeenCalled();
  });

  it('should attempt to reconnect every 5 seconds', function (done) {
    setTimeout(function () {
      expect(ws.calls.count()).toBe(2);
      done();
    }, 6000);
  });
});

should attempt to connect on load

passes: it was called once as expected.

should attempt to reconnect every 5 seconds

fails: no matter what timeout period I pass to setTimeout it's only ever called once. I'm wondering if this is due to the socket being re-instantiated every reconnect attempt with the new keyword. I'm not really familiar with how using new in javascript differs to using a normal function to construct an object.

Am I missing something? Or is the browser's WebSocket just a pain to test around?

Upvotes: 1

Views: 1957

Answers (1)

Michal Charemza
Michal Charemza

Reputation: 27062

A way to avoid the issue with timeout in the test is to make it fully synchronous by calling $interval.flush to force application time to move forward

it('should attempt to reconnect every 5 seconds', function () {
  $interval.flush(4999);
  expect(ws.calls.count()).toBe(1);
  $interval.flush(1);
  expect(ws.calls.count()).toBe(2);
  $interval.flush(4999);
  expect(ws.calls.count()).toBe(2);
  $interval.flush(1);
  expect(ws.calls.count()).toBe(3);
});

You can then simulate 10 seconds worth of application time in a few milliseconds of wallclock time. You can see this in action at http://plnkr.co/edit/lz2u08JQLGCgGvX3vKQx

My suspicion as to why the original didn't work, is that the mock implementation of $interval in the testing environment doesn't actually call the real Javascript setInterval/setTimeout at all: it just provides the flush functionality to move it forward a simulated amount of time.

Upvotes: 2

Related Questions