使用postMessage和addEventListener进行Jasmine单元测试

时间:2015-09-12 23:33:05

标签: javascript angularjs unit-testing jasmine

我试图使用postMessage和addEventListener对情境进行单元测试。用例是我使用单独的窗口进行用户登录,类似于OAuth工作流,然后在登录窗口中使用postMessage通知主窗口用户已登录。这是将在主窗口中的监听代码:

$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);
    });
});

这两种尝试都没有做任何事情。我仍然得到相同的错误消息。我已经尝试了其他调试步骤,例如使用spyOn for $ location.path(),因此它返回一个样本值,如&#34; / fake&#34;。在我直接测试服务方法而不是使用postMessage来触发服务代码的所有其他单元测试中,存根工作正常。但是,在addEventListener函数中,$ location.path()返回&#34;&#34;这指出了一个理论,即addEventListener函数在一个完全不同于单元测试准备的实例中运行。这可以解释为什么没有使用存根函数以及为什么这个其他实例试图加载虚假模板。这discussion也巩固了理论。

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

1 个答案:

答案 0 :(得分:6)

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

看看你的要点,你可能需要再模拟一些服务,但这应该足以测试“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();
    }));
});