【问题标题】:Jasmine - correctly mocking an object with a constructorJasmine - 使用构造函数正确模拟对象
【发布时间】:2015-07-21 07:19:09
【问题描述】:

我试图在 Angular 的 jasmine 测试中模拟原生 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(评估 'this.onmessage(message)')

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

我尝试了几种不同的方法来做到这一点,比如打电话给ws.prototype.onmessage 或只是ws.onmessage

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

函数(消息){ ... }

未定义

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

【问题讨论】:

    标签: javascript angularjs unit-testing mocking jasmine


    【解决方案1】:

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

    您想要做的是从测试套件手动触发onmessage,但onmessage 附加到sock 实例,并且因为您实际上无权从测试套件访问sock 实例,因此您也无权访问onmessage

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

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

    JavaScript 有一个 特性 - 你可以从构造函数 (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 () {}; 时,您实际上将其分配给返回的显式injectedWs,并且它将在我们的测试套件中可用,因为JavaScript 的另一个功能 - 对象通过引用传递。

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

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

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

    【讨论】:

    • 感谢您的详细回答。在您看来,这是首选的方式吗?还是我应该考虑将 WebSocket 包装在另一层抽象中以消除构造函数的挫败感?
    • 我认为包装它会更好,不仅是为了更容易测试,而且对于你想使用另一个套接字实现(可能是一些供应商库)的情况。这样,您只需更改包装器的代码。此外,您应该重新考虑您希望如何实现包装器,因为如果它再次成为构造函数,您将遇到同样的麻烦。它可能是一个单例服务,其方法包括wsService.create('ws://...')wsService.on('message', fn)wsService.emit('message')
    • 我无意用构造函数创建任何东西!感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 2015-09-18
    • 2019-08-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多