How to mock socket.io with Angular and Jasmine

2019-05-28 13:27发布

I'm having trouble figuring out how to correctly mock Socket.io in an Angular application using Jasmine and Karma.

Here are the files found in 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'

Here is how my controller looks:

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

Here is my socket factory:

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

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

Here is the main angular module:

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

Here is the test I'm having trouble figuring out:

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

I suspect that socket isn't being properly injected and I'm not sure how to fix it. Similarly, I'd also like to be able to expect that a call to socket.emit is made but don't know how.

Any help would be appreciated.

EDIT: I'd like to use this socket mock if possible but I'm having trouble figuring out exactly how it works.

2条回答
甜甜的少女心
2楼-- · 2019-05-28 13:53

I ended up using the sockMock object defined in this solution.

查看更多
3楼-- · 2019-05-28 14:05

I would use Sinon.js to create a mock socket object.

For example, in your beforeEach, initialize socket to be an empty object:

socket = {};

createController() must pass the mock to the controller:

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

Then in your test do something like this:

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

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

You will need the packages sinon and jasmine-sinon. Also if you are using Karma, it will need karma-sinon. You can find more guidelines on the setup on the respective pages.

EDIT: In your case your mock needs to have on and emit functions every time, as they are needed by the controller. In this case it might be better to put this into the beforeEach function:

socket = {
  emit : sinon.spy(),
  on : sinon.spy()
};
查看更多
登录 后发表回答