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.
I ended up using the
sockMock
object defined in this solution.I would use Sinon.js to create a mock socket object.
For example, in your
beforeEach
, initialize socket to be an empty object:createController()
must pass the mock to the controller:Then in your test do something like this:
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
andemit
functions every time, as they are needed by the controller. In this case it might be better to put this into thebeforeEach
function: