Jasmine 单元测试与 postMessage 和 addEventListener

Jasmine Unit Test with postMessage and addEventListener

我正在尝试使用 postMessage 和 addEventListener 对情况进行单元测试。用例是我使用一个单独的 window 用于类似于 OAuth 工作流程的用户登录,然后在登录 window 中使用 postMessage 通知主 window 用户已经登录。这是主要 window:

中的监听代码
$window.addEventListener("message", function(event) {
    if (event.data.type === "authLogin") {
        service.curUser = event.data.user;
        utilities.safeApply($rootScope);
        $window.postMessage({type: "authLoginSuccess"}, '*');
    }
});

utilities.safeApply函数定义为:

// Run $apply() if not already in digest phase.
utilitiesService.safeApply = function(scope, fn) {
    return (scope.$$phase || scope.$root.$$phase) ? scope.$eval(fn) : scope.$apply(fn);
};

我的单元测试是为了发送一个postMessage来模拟登录:

describe('auth login postMessage', function() {
    var testUser = {handle: 'test'};
    beforeEach(function(done) {
        $window.postMessage({type: 'authLogin', user: testUser}, '*');
        function onAuthLoginSuccess(event) {
            $window.removeEventListener('message', onAuthLoginSuccess);
            done();
        }
        $window.addEventListener("message", onAuthLoginSuccess);
    });
    it("should set the user object", function() {
        expect(service.curUser).toEqual(testUser);
    });
});

这是运行单元测试的结果:

12 09 2015 14:10:02.952:INFO [launcher]: Starting browser Chrome
12 09 2015 14:10:05.527:INFO [Chrome 45.0.2454 (Mac OS X 10.10.5)]: Connected on socket 537CxfI4xPnR0yjLAAAA with id 12583721
Chrome 45.0.2454 (Mac OS X 10.10.5) ERROR
  Uncaught Error: Unexpected request: GET security/loginModal.tpl.html
  No more request expected
  at http://localhost:8089/__test/Users/userX/esupport/code/proj/public/vendor/angular-mocks/angular-mocks.js:250
Chrome 45.0.2454 (Mac OS X 10.10.5): Executed 23 of 100 (skipped 60) ERROR (0.333 secs / 0.263 secs)

我不明白为什么它会尝试加载 HTML 模板。我已经缩小了它的范围,这样如果我不调用 $scope.$apply() 函数,单元测试就会成功而不会出错。但是,我需要 $apply() 来更新视图。

我已经尝试在单元测试中删除 utilities.safeApply 方法,还尝试为 HTML 模板 GET 请求设置期望。这些尝试看起来像:

describe('auth login postMessage', function() {
    var testUser = {handle: 'test'};
    beforeEach(function(done) {
        $httpBackend.when('GET', 'security/loginModal.tpl.html').respond('');  // <-- NEW
        spyOn(utilities, 'safeApply').and.callFake(angular.noop);  // <-- NEW
        window.postMessage({type: 'authLogin', user: testUser}, '*');
        function onAuthLoginSuccess(event) {
            window.removeEventListener('message', onAuthLoginSuccess);
            done();
        }
        window.addEventListener("message", onAuthLoginSuccess);
    });
    it("should set the user object", function() {
        expect(service.curUser).toEqual(testUser);
    });
});

这两种尝试都无济于事。我仍然收到相同的错误消息。我尝试了其他调试步骤,例如对 $location.path() 使用 spyOn,因此它 returns 是一个像“/fake”这样的示例值。在我直接测试服务方法而不使用 postMessage 触发服务代码的所有其他单元测试中,存根工作正常。但是,在 addEventListener 函数中, $location.path() returns "" 指向一个理论,即 addEventListener 函数 运行ning 在一个与单元测试完全不同的实例中准备好了。这可以解释为什么没有使用被删除的函数以及为什么这个其他实例试图加载伪造的模板。这discussion也巩固了理论。

所以现在的问题是,我该如何让它发挥作用?即,如何在使用存根函数的同一实例中将 addEventListener 函数获取到 运行,并且它不向 HTML 模板发出请求?

我只是模拟所有外部部件并确保最终结果符合您的预期。

根据您的要点,您可能需要模拟更多服务,但这应该足以测试 "authLogin" 消息事件。

describe('some test', function() {
    var $window, utilities, toaster, securityRetryQueue, service, listeners;

    beforeEach(function() {
        module('security.service', function($provide) {
            $provide.value('$window',
                $window = jasmine.createSpyObj('$window', ['addEventListener', 'postMessage']));
            $provide.value('utilities',
                utilities = jasmine.createSpyObj('utilities', ['safeApply']));
            $provide.value('toaster',
                toaster = jasmine.createSpyObj('toaster', ['pop']));
            $provide.value('securityRetryQueue',
                securityRetryQueue = jasmine.createSpyObj('securityRetryQueue', ['hasMore', 'retryReason']));

            // make sure you're not fetching actual data in a unit test
            securityRetryQueue.onItemAddedCallbacks = [];
            securityRetryQueue.hasMore.and.returnValue(false);

            $window.addEventListener.and.callFake(function(event, listener) {
                listeners[event] = listener;
            });
        });

        inject(function(security) {
            service = security;
        });
    });

    it('registers a "message" event listener', function() {
        expect($window.addEventListener).toHaveBeenCalledWith('message', listeners.message, false);
    });

    it('message event listener does stuff', inject(function($rootScope) {
        var event = {
            data: {
                type: 'authLogin',
                user: 'user'
            },
            stopPropagation: jasmine.createSpy('event.stopPropagation')
        };

        listeners.message(event);

        expect(service.curUser).toBe(event.data.user);
        expect(toaster.pop).toHaveBeenCalledWith('success', 'Successfully logged in.');
        expect(utilities.safeApply).toHaveBeenCalledWith($rootScope);
        expect($window.postMessage).toHaveBeenCalledWith({type: "authLoginSuccess"}, '*');
        expect(event.stopPropagation).toHaveBeenCalled();
    }));
});