The project I am working on makes use of socket.io for some of the components of the UI.
I am trying to write unit tests for this particular section of the application. I am using: angular-socket-io and angular-socket.io-mock to mock the server side component.
I am using everything at the simplest level, so I have my factory:
myapp
.factory('notify', function (socketFactory) {
return socketFactory();
});
This is the controller
myapp
.controller('NotificationsCtrl', function ($scope, notify) {
$scope.items = []
notify.emit("loadItems",{},function(res){
$scope.items = res.res
});
});
and finally the unit test:
describe('Controller: NotificationsCtrl', function () {
// load the controller's module
beforeEach(module('app'));
var NotificationsCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
NotificationsCtrl = $controller('NotificationsCtrl', {
$scope: scope
});
}));
it('The scope.items should change somehow', function() {
expect(scope.items.length).toEqual(3);
});
});
I cannot realize what is missing to make this working.
How should I change my code to make it happen?
Thanks
Related
I am trying to integrate Karma and Jasmine in to my project.
I have started off with a very basic test to ensure my controller is defined and a $scope variable equals a string - which pass as expected.
My controller, also calls a service which performed a $http.get, when running my test, without any mention of a service, i get the error:
Error: Unexpected request: GET /my/endpoint/
No more request expected
Controller:
define(['module'], function (module) {
'use strict';
var MyController = function ($scope, MyService) {
$scope.testScope = 'karma is working!';
MyService.getData().then(function (data) {
$scope.result = data.hour
});
};
module.exports = ['$scope', 'MyService', MyController ];
});
Test:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope;
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_) {
scope = _$rootScope_.$new();
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
Are the above errors expected?
UPDATE from response below:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, $httpBackend, myService;
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_, _myService_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
$httpBackend.expectGET("/my/endpoint");
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
Using Angular Mocks you will always get an error if there is an unexpected or incorrect http request attempted -- even for templates. In your case there are two ways to handle this for testing:
use $httpBackend
$httpBackend was designed for testing http requests without actually hitting the wire. In your test, simply add
$httpBackend.expectGET("/my/endpoint");
before you initialize the controller.
Mock the service
The service itself is making the http request, so you can mock the service instead. Services will be injected automatically as usual, but you can explicitly injection whatever you want:
controller = _$controller_('MyController', {$scope: scope,
MyService: {getData: () => ({then: () => {}}) });
This injects an object that has a getData function which returns an object with .then function. Of course this doesn't come close to implementing what you are trying to do, but it is another way to perform the test.
Both of the above approaches are valid. It depends on what you are testing and what you are trying to accomplish with the testing.
I've been trying to get started with unit testing in angular with karma and jasmine, and i've been pulling my hair out trying to wrap my head around how to test controllers with dependencies. I tried mocking a spy with a jasmine spyObj and registering it in the beforeEach hook, but for some reason the spy isn't being recognized.
Here's the code:
angular.module('testModule', [])
.controller('TestController', [
'$scope',
'TestService',
function ($scope, TestService) {
$scope.data = TestService.load();
}])
.factory('TestService', function () {
return {
load: function(){
return "foo";
}
}
});
and here's the test
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'), function($provide){
TestService = jasmine.createSpyObj("TestService", ["load"]);
TestService.load.andReturn("bar");
$provide.value("TestService", TestService)
});
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
}); });
Both assertions in the test fail.
I get 'Error: Expected a spy, but got Function' when i call expect(TestService.load).toHaveBeenCalled();
and if I call expect($scope.data).toEqual("bar"), I get Expected 'foo' to equal 'bar'. "Foo" is coming from the actual service, not the spy object.
Thanks for your help.
Instead of jasmine.createSpyObj, it will be easier to use the existing service that the $injector provides and then just mock the single method. You can achieve this with spyOn instead:
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'));
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
spyOn(TestService, 'load').and.returnValue('bar');
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
});
});
In your beforeEach you are injecting in _TestService_ and then overwriting the one you declared in the previous beforeEach via:
TestService = _TestService_;
Remove that code and your test should succeed.
Also there is no need to do this:
$provide.value("TestService", TestService)
Basically you're trying to use Angular's dependency injection when you're manually injecting things which is unnecessary.
In our project, we use requirejs with angularjs. We have a main application module (app) and module for all services (app-services), module for all controllers (app-controllers), module for all filters (app-filters). Modules app-controllers, app-services etc.. are added as dependencies to main app module.
main application module
var mainAppModule = angular.module('app', [
'ngResource',
'ngSanitize',
'app.controllers',
'app.directives',
'app.services',
'app.filters',
'app.routes'
]);
mainAppModule.run(['$location', '$rootScope', function ($location, $rootScope) {
$rootScope.sayHello = function(name) {
console.log("Hello" + name);
}
}]);
How can write the Karma/Jasmine tests for mainAppModule.run method?
Generally, logic should remain outside run method (e.g. included in controllers, services, directives, filters, etc.). You can, however, test your run method as follows. Using jasmine syntax:
//Updating this method to use $log for DI
mainAppModule.run(['$location', '$rootScope', '$log', function ($location, $rootScope, $log) {
$rootScope.sayHello = function(name) {
$log.info("Hello" + name);
}
}]);
//---------------------------
//Jasmine test
describe("app run", function () {
var $rootScope;
var $log;
beforeEach(module("app"));
beforeEach(inject(function (_$rootScope_, _$log_) {
$rootScope = _$rootScope_;
$log = _$log_;
}));
it("should expose sayHello function to $rootScope", function () {
expect(angular.isFunction($rootScope.sayHello)).toBe(true);
});
describe("sayHello function", function () {
it("should log 'Hello name'", function () {
spyOn($log, "info");
$rootScope.sayHello("test");
expect($log.info).toHaveBeenCalledWith("Hello test");
});
});
});
I believe that the best way is simply to use state-based verification that the "run" method executed. In the case of the specific example you provided, inject $rootScope into a test and verify that it has a property named "sayHello" of type function.
IIRC your application run methods should be called automatically by Jasmine when you call the angular.mock.module function from your test.
I am trying to write unit tests for the AngularJS application. Below is a pretty standard test template, which works fine:
describe('Controller: MainCtrl', function () {
var MainCtrl, scope;
beforeEach(function() { // <-- what if I remove this
module('watcomApp');
inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope,
});
});
}); // <-- and this
it('some spec', function () {
expect(scope.data).toEqual('something');
});
});
However if I try to reuse the current state of controller and remove beforeEach:
describe('Controller: MainCtrl', function () {
var MainCtrl, scope;
module('watcomApp');
inject(function ($controller, $rootScope) {
...
it stops working because scope.data becomes undefined.
The question is: what happens to scope? I expect it to persist between specs since it's global.
Can’t figure out how to make controller tests working. I am playing with ngStart seed project. So I forked the repository (https://github.com/Havrl/ngStart ) and want to create a very basic unit test.
My controller test file:
define(function() {
"use strict";
describe("the contactcontroller", function () {
var contactController, scope;
beforeEach(function () {
module("contact");
inject(["ContactController", function (_contactController) {
contactController = _contactController;
}]);
});
it("should give me true", function () {
expect(true).toBe(true);
});
});
});
But that is not working.
What am I missing?
as answered in the following question the controller needs to be manually instantiated with a new scope:
how to test controllers created with angular.module().controller() in Angular.js using Mocha
Additionally the project you are using (its mine :-) ) is defining the controllers inside route definitions and not with a call to angular.controller(...).
The downside is that the controllers are not known by name to angularJS (afaik), so the code from the answer above would not work:
ctrl = $controller("ContactController", {$scope: scope });
Instead you have to load the controller explicitely with requireJS inside your test file and give the function to the $controller(..) call, like this:
define(["ContactController"], function(ContactController) {
"use strict";
describe("the contactcontroller", function () {
var contactController, scope;
beforeEach(function () {
module("contact");
inject(["$rootScope", "$controller", function ($rootScope, $controller) {
scope = $rootScope.$new();
contactController = $controller(ContactController, {$scope: scope});
}]);
});
....
});