Jasmine - 使用构造函数正确模拟对象

Jasmine - correctly mocking an object with a constructor

我试图在 Angular 的茉莉花测试中模拟本机 WebSocket,我可以监视构造函数和 send 函数,但我不知道如何欺骗onmessage.

的调用

WebSocket 被提取到一个 Angular 常量:webSocket.

我的测试是这样的:

describe('Data Service', function () {

  var dataService,
    $interval,
    ws;

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

    inject(function (_dataService_, _$interval_) {
      dataService = _dataService_;
      $interval = _$interval_;
    });
  });

  it("should call subscribers when a message is received", function () {
    var callback = jasmine.createSpy('onMessage callback');

    function message(type) {
      return {
        data: {
          type: type,
          data: 'data'
        }
      };
    }

    // Subscribe to messages via the exposed function.
    // Subscribe to one of them twice to test that all subscribers are called and not just the first one.
    dataService.onMessage(21, callback);
    dataService.onMessage(21, callback);
    dataService.onMessage(22, callback);
    dataService.onMessage(23, callback);

    // Pick 3 numbers that are valid data types to test.
    ws.receiveMessage(message(21));
    ws.receiveMessage(message(22));
    ws.receiveMessage(message(23));

    expect(callback.calls.count()).toBe(4);
    expect(callback.calls.allArgs()).toBe([message(21).data, message(21).data, message(22).data, message(23).data]);
  });
});

我的代码如下所示:

angular.module('core', []).constant('webSocket', WebSocket);

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

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

    sock.onmessage = function (message) {
      var json = JSON.parse(message.data);

      onMessageSubscribers[json.type].forEach(function (sub) {
        sub(json);
      });
    };
  }

  function onMessage(type, func) {
    onMessageSubscribers[type].push(func);
  }

  openSocket();

  return {
    onMessage: onMessage
  };
});

onMessageSubscribers 是一个已定义的数组,其中包含正确的类型(int 键),但它与问题无关。

我收到以下错误:

TypeError: 'undefined' is not a function (evaluating 'this.onmessage(message)')

onmessage 似乎由测试前运行的 angular 代码定义,但我想知道这是否与构造对象与常规对象的不同之处有关JS,我真的没有任何经验。

我尝试了几种不同的方法来做到这一点,比如调用 ws.prototype.onmessage 或只调用 ws.onmessage.

如果我在 dataService 中的适当位置放置一个 console.log(sock.onmessage);,并且在测试中调用 onmessage 之前再记录另一个日志:

function (message) { ... }

undefined

如何强制调用 onmessage 或任何其他 WebSocket 事件?

当您调用 var sock = new webSocket(); 时,正在从 webSocket 构造函数创建新实例。由于您的设计,此 sock 变量(webSocket 实例)未公开公开,因此在测试套件中不可用。然后,您在此 sock 实例上定义一个 属性 onmessagesock.onmessage = function () { ... }.

你想做的是从测试套件手动触发 onmessage,但是 onmessage 附加到 sock 实例,因为你实际上没有访问权限sock 来自测试套件的实例,因此您也无权访问 onmessage

您甚至无法从原型链访问它,因为您手头没有实际的 sock 实例。

我想出的东西真的很棘手。

JavaScript 有一个 特征 - 你可以明确地 return 来自构造函数(info here)的对象,它将被分配给一个变量而不是新的实例对象。例如:

function SomeClass() { return { foo: 'bar' }; }
var a = new SomeClass();
a; // { foo : 'bar' }; }

下面是我们如何使用它来访问 onmessage:

var ws,
    injectedWs;

beforeEach(function () {
    module('core', function ($provide) {

        // it will be an explicitly returned object
        injectedWs = {};

        // and it is a constructor - replacer for native WebSocket
        ws = function () {
            // return the object explicitly
            // it will be set to a 'sock' variable
            return injectedWs;
        };

        $provide.constant('webSocket', ws);
});

因此,当您在 DataService 中执行 sock.onmessage = function () {}; 时,您实际上将其分配给了 returned injectedWs 的显式,并且它将在我们的测试套件,因为 JavaScript 另一个 特性 - 对象通过引用传递。

最后,您现在可以在测试中随时调用 onmessage 使用 injectedWs.onmessage(message)

我已将所有代码移至此处:http://plnkr.co/edit/UIQtLJTyI6sBmAwBpJS7

注意:套件预期和 JSON 解析存在一些问题,可能是因为您还无法访问此代码,我已经修复它们以便测试可以 运行。