如何使用 Angular 和 Jasmine 模拟 socket.io

Posted

技术标签:

【中文标题】如何使用 Angular 和 Jasmine 模拟 socket.io【英文标题】:How to mock socket.io with Angular and Jasmine 【发布时间】:2015-05-04 22:40:53 【问题描述】:

我无法弄清楚如何使用 Jasmine 和 Karma 在 Angular 应用程序中正确模拟 Socket.io。

这是在karma.conf.js中找到的文件:

'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/angular-ui-router/release/angular-ui-router.js',
'bower_components/angular-socket-io/socket.js',
'bower_components/socket.io-client/socket.io.js',
'bower_components/angular-socket-io/mock/socket-io.js',
'public/javascripts/*.js',
'ng_tests/**/*.js',
'ng_tests/stateMock.js'

这是我的控制器的外观:

var lunchrControllers = angular.module('lunchrControllers', []);

lunchrControllers.controller('UserMatchingController', ['$state', 'socket',
    function ($state, socket) 
        socket.emit('match');

        socket.on('matched', function (data) 
            $state.go('users.matched', name: data.name)
        );
    ]);

这是我的套接字工厂:

var lunchrFactories = angular.module('lunchrFactories', []);

lunchrFactories.factory('socket', function (socketFactory) 
    return socketFactory();
);

这是主要的角度模块:

var lunchrApp = angular.module('lunchr', ['ui.router', 'btford.socket-io', 'lunchrControllers', 'lunchrFactories' ]);

lunchrApp.config(['$stateProvider', '$urlRouterProvider', '$locationProvider',
    function ($stateProvider, $urlRouterProvider, $locationProvider) 
        $urlRouterProvider.otherwise('/');

        $stateProvider.
            state('mainPage', 
                url: '/',
                templateUrl: '/partials/main.jade',
                controller: 'MainPageController'
            )
            .state('users', 
                url: '/users',
                templateUrl: '/partials/users.jade',
                controller: 'UserController'
            )
            .state('users.matching', 
                url: '/matching',
                templateUrl: '/partials/users.matching.jade',
                controller: 'UserMatchingController'
            )
            .state('users.matched', 
                url: '/matched',
                templateUrl: '/partials/users.matched.jade',
                params:name:null,
                controller: 'UserMatchedController'
            )
            .state('register', 
                url:'/register',
                templateUrl: 'partials/register.jade',
                controller: 'RegisterController'
            );
        $locationProvider.html5Mode(true);
    ]);

这是我无法弄清楚的测试:

'use strict';

describe('UserMatchingController', function () 
    beforeEach(module('lunchr'));
    beforeEach(module('stateMock'));


    var $controller, $rootScope, $state, socket;

    beforeEach(inject(function ($injector) 
        // Get hold of a scope (i.e. the root scope)
        $rootScope = $injector.get('$rootScope');

        $state = $injector.get('$state');

        // The $controller service is used to create instances of controllers
        $controller = $injector.get('$controller');

        socket = $injector('socket');
    ));

    afterEach(function () 
        $state.ensureAllTransitionsHappened();
    );

    function createController() 
        return $controller('UserMatchingController', '$scope': $rootScope);
    

    describe('on initiazation', function()
        it('socket should emit a match', function() 
            createController();

            // would like to be able to expect a socket.emit call
        );
        it('should transition to users.matched upon receiving matched', function()
            createController();
            $state.expectTransitionTo('user.matched');
            socket.receive('matched', name: 'Ben'); //This errors out with: cannot read property 'receive' of undefined
        )
    )
);

我怀疑socket 没有正确注入,我不确定如何修复它。同样,我也希望能够期待调用socket.emit,但不知道如何。

任何帮助将不胜感激。

编辑:如果可能,我想使用this socket mock,但我无法弄清楚它是如何工作的。

【问题讨论】:

【参考方案1】:

我会使用Sinon.js 创建一个模拟套接字对象。

例如,在您的beforeEach 中,将套接字初始化为空对象:

socket = ;

createController() 必须将模拟传递给控制器​​:

$controller('UserMatchingController', '$scope': $rootScope, 'socket': socket);

然后在你的测试中做这样的事情:

socket.emit = sinon.spy(); //Create a spy so we can check if it was called
createController();

expect(socket.emit).toHaveBeenCalledWith('match');

您将需要包sinon 和jasmine-sinon。此外,如果您使用 Karma,则需要 karma-sinon。您可以在各个页面上找到有关设置的更多指南。

编辑:在您的情况下,您的模拟每次都需要具有 onemit 函数,因为控制器需要它们。在这种情况下,最好将其放入beforeEach 函数中:

socket = 
  emit : sinon.spy(),
  on : sinon.spy()
;

【讨论】:

啊,有道理。两个问题:你建议使用诗浓的spy函数和茉莉花的函数有什么原因吗?另外,我添加的修改会影响您的推荐吗? 您链接的那个模拟需要您创建其中一个,将其作为依赖项传递给控制器​​,然后在测试后确认发出的哈希包含某些内容。我个人更喜欢使用 sinon。在这种情况下,我只需要知道是否调用了 ton 就使用了 spy。如果您希望它调用回调(例如对于事件),您应该使用sinon.stub().callsArgWith(0, mockData) 。我实际上没有使用过 Jasmine 的间谍,我猜两者都可以。【参考方案2】:

我最终使用了this solution 中定义的sockMock 对象。

【讨论】:

以上是关于如何使用 Angular 和 Jasmine 模拟 socket.io的主要内容,如果未能解决你的问题,请参考以下文章

模拟指令以测试组件 - Angular 8 与 jasmine 和 Karma

如何模拟在 AngularJS Jasmine 单元测试中返回承诺的服务?

在 Jasmine 单元测试中模拟 AngularJS 模块依赖项

使用 jasmine / karma 进行 Angular 4 单元测试和 http post mocking - 如何修复

使用 Jasmine 间谍进行 Angular 2 组件测试“没有 Http 提供者!”错误

如何使用 angular9 和 Jasmine 为所有组件生成 .pdf 格式的单元测试用例代码覆盖率报告