angular.mock.inject causing $injector:moduleerr - angularjs

I am setting up unit tests using Karma, Require.js, Mocha and Chai. After setting everything up, I have a simple spec file to test a controller and a scope variable.
I know an inject function needs to be used to get $controller service that will instantiate the controller. I am unable to do this. Here is what I have :
define([
'angular',
'angularMocks'
], function() {
describe('MyCtrl', function() {
var $controller;
beforeEach(angular.mock.module('MyApp'));
// ////////// ATTEMPT 1
// beforeEach(angular.mock.inject(function(_$controller_){
// $controller = _$controller_;
// }));
// ////////// ATTEMPT 2
// it('should', inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
// }));
it('should', function() {
// ////////// GOAL
expect($scope.message).to.equal('hello world');
});
});
});

Related

Angular Mocking in Jasmine Unknown provider

I can't get these two spec files to play well with each other. I didn't think spec files would effect other spec files but in this case it seem like they do, it makes no sense to me.
I'm using Jasmine and Karma the tests are automated with Gulp
The error I'm getting is "Unknown provider: ProductServiceProvider <- ProductService"
I have changed the tests to troubleshoot the issue here is the simple versions.
If I comment out the following line in file 2 both files pass.
angular.module('eu.product.service', []);
It has something to do with mocking the module but I can't figure out what I'm doing wrong here.
spec file 1
describe('Testing euProduct', function(){
var $factory;
var $httpBackend;
beforeEach(function () {
//modules
module('eu.product.service');
//injections
inject(function($injector){
$factory = $injector.get('ProductService');
$httpBackend = $injector.get('$httpBackend');
});
//mock data
$httpBackend.when('GET', '/Mercury/product/list/0/0?PrimaryCategoryID=0&pageSize=20&startPage=1').respond({
"data":
[{
"recallid":45,
}]
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
//-----Tests----
it('Should be able to get data from the server on default parameters.', function(){
$factory.list({},function(data){
expect(data.data[0].recallid).toBe(45);
});
$httpBackend.flush();
});
});
Spec file 2
'use strict';
describe('Testing euProduct Logic', function(){
//variables in closure scope so they can be used in tested but set with injection in beforeEach
var $factory;
//mocking a module :: http://www.sitepoint.com/mocking-dependencies-angularjs-tests/
beforeEach(function () {
angular.module('eu.product.service',[]);
module(function($provide) {
$provide.factory('ProductService', function() {
// Mocking utilSvc
return {
list : function(para, callback){
callback({
data : {
product : 'The product Name'
}
})
}
};
});
$provide.service('storageSvc', function() {
// Mocking storageSvc
});
});
//modules
module('eu.product.logic');
//injections
inject(function($injector){
$factory = $injector.get('ProductLogic');
});
});
//-----Tests----
it('Should be able to run tests', function(){
expect(2).toBe(2);
});
});
Both module and inject from angular-mocks return functions which need to be called.
In the following example I made these changes:
Refactor to a basic working example
Don't define custom $-prefixed variables. These are reserved by angular.
Use inject to inject instead of $injector.
Add some comments for further explanation.
describe('ProductService', function() {
var ProductService;
var $httpBackend;
// Start module config phase.
beforeEach(module('eu.produce.service', function($provide) {
// Inject providers / override constants here.
// If this function is empty, it may be left out.
}))
// Kickstart the app and inject services.
beforeEach(inject(function(_ProductService_, _$httpBackend_){
ProductService = _ProductService_;
$httpBackend = _$httpBackend_;
});
beforeEach(function() {
// Optionally use another beforeEach block to setup services, register spies, etc.
// This can be moved inside of the inject function as well if you prefer.
//mock data
$httpBackend.when('GET', '/Mercury/product/list/0/0?PrimaryCategoryID=0&pageSize=20&startPage=1').respond({
"data":
[{
"recallid":45,
}]
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
//-----Tests----
it('Should be able to get data from the server on default parameters.', function(){
ProductService.list({},function(data){
expect(data.data[0].recallid).toBe(45);
});
$httpBackend.flush();
});
});

Can't instantiate angular controller in karma unit tests

I'm facing some trouble with a generated project (by yoeman) and it's testing.
What I want is to build a fully automated testing environement. I use Gulp, Karma, Jasmine and angular-mocks.
TypeError: undefined is not an object (evaluating 'controller.awesomeThings')
In my console after 'gulp test' it throws this error message. but this doesn't make any sense for me.
Look at my test spec:
'use strict';
describe('Controller: AboutCtrl', function() {
// load the controller's module
beforeEach(function() {
module('evoriApp');
});
var controller;
var scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
controller = $controller('AboutCtrl', {
'$scope': scope
});
}));
it('should attach a list of awesomeThings to the scope', function() {
console.log(controller);
expect(controller.awesomeThings.length).toBe(3);
});
});
And karma should have any file it needs (karma.conf.js):
files: [
// bower:js
'bower_components/angular/angular.js',
'bower_components/angular-animate/angular-animate.js',
'bower_components/angular-aria/angular-aria.js',
'bower_components/angular-cookies/angular-cookies.js',
'bower_components/angular-messages/angular-messages.js',
'bower_components/angular-resource/angular-resource.js',
'bower_components/angular-route/angular-route.js',
'bower_components/angular-sanitize/angular-sanitize.js',
'bower_components/angular-material/angular-material.js',
// endbower
'app/scripts/**/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
This is how the generated gulp method looks like:
gulp.task('test', ['start:server:test'], function () {
var testToFiles = paths.testRequire.concat(paths.scripts, paths.mocks, paths.test);
return gulp.src(testToFiles)
.pipe($.karma({
configFile: paths.karma,
action: 'watch'
}));
});
I don't know where the failure is. I checked all paths and rewrote the testfiles multiple times... But the error doesn't change.
Does anybody have an idea, what the error could be caused by?
Ok guys, I got it.
I tried to run the test in the jasmine standalone environement to minimies the area where the error root is.
This is what my controller looks like:
angular.module('evoriApp')
.controller('AboutCtrl', function ($scope) {
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
$scope.testVariable = 2;
});
I rewrote the 'this.awesomeThings' to '$scope.awesomeThings' and didn't get it, that this is something different. Whatever...
This is the now running spec:
describe('Controller: AboutCtrl', function() {
// load the controller's module
beforeEach(function() {
module('evoriApp');
console.log("1");
});
var controller;
var scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
controller = $controller('AboutCtrl', {
$scope: scope
});
}));
it('sollte instanziert worden sein', function() {
expect(controller).toBeDefined();
});
it('sollte ein Object "awesomeThings" besitzen', function() {
expect(scope.awesomeThings).toBeDefined();
});
it('should attach a list of awesomeThings to the scope', function() {
expect(scope.awesomeThings.length).toBe(3);
});
});
What I learned: In a test you have to generate the scope, which you pass to the controller, by yourself and afterward you got to use the passed scope variable for $scope.variables.

How to mock services in directive tests?

i have a directive with the injected apiService which is a wrapper around $http and others custom services.
when I try to test the directive, I don't know how to mock the service:
app.directive('coreMenu', ['apiService', function (apiService) {
return {
restrict : 'E',
replace : true,
templateUrl : 'Directives/CoreMenu.tpl.html',
link: function (scope) {
apiService.get({
module: 'Core',
route: 'Menu'
}).then(function (response) {
scope.menuItems = response.data;
}, function (response) {
// error: no menu available
});
}
};
}]);
Test:
describe('coreMenu directive', function() {
var $compile, $rootScope;
beforeEach(function () {
module('AdminTechPortal');
module('CoreLeftMenu.tpl.html');
inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
});
it('replaces the element with the appropriate content', function() {
var element = $compile("<core-menu></core-menu>")($rootScope);
$rootScope.$digest();
expect(element.html()).toContain('id="core-menu"');
});
});
This test throws ( which is normal):
Error: Unexpected request: GET /Core/Menu/
Is it possible to mock apiService and not just the xhr call using $httpbackend ?
What I do in such cases follows... Bear in mind that testing promises (this way) has a bit of work and will fail if the promise needs to be chained!
describe('coreMenu directive', function() {
var $compile, $rootScope, apiServiceMock;
// BIG WARNING:
// This beforeEach() block MUST run before using any inject()
beforeEach(function () {
apiServiceMock = {};
apiServiceMock.get = jasmine.createSpy('apiServiceMock.get').and.callFake(function() {
return { // mock the promise
then: function(successCb, errorCb) {
// keep the callbacks to call them at our convenience
apiServiceMock._successCb = successCb;
apiServiceMock._errorCb = errorCb;
}
};
});
module(function($provide) {
// override the apiService in the DI container
$provide.value('apiService', apiServiceMock);
});
});
beforeEach(function () {
module('AdminTechPortal');
module('CoreLeftMenu.tpl.html');
inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
});
});
it('replaces the element with the appropriate content', function() {
var element = $compile("<core-menu></core-menu>")($rootScope);
$rootScope.$digest();
// SAMPLE TESTS
expect(apiServiceMock.get).toHaveBeenCalledWith(...);
// test success
apiServiceMock._successCb(...);
expect(...) // things to expect on success
// test failure - probably in another it()
apiServiceMock._errorCb(...);
expect(...) // things to expect on error
});
});
Follow these steps:
add $httpBackend variable:
var $compile, $rootScope, $httpBackend;
Inject and assign on beforeEach
inject(function (_$compile_, _$rootScope_, _$httpBackend_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
});
Create an afterEach
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
test
it('replaces the element with the appropriate content', function() {
$httpBackend.expectGET('/Core/Menu/');
var element = $compile("<core-menu></core-menu>")($rootScope);
$httpBackend.flush();
$rootScope.$digest(); // Not sure if still needed
expect(element.html()).toContain('id="core-menu"');
});

How to test saving a resource in a controller with a promise

I have a controller that saves a resource. I can't tell how to "access" the part of the code that executes after the promise resolves. What do I need to change about my test or controller in order to get it to work? Here's the code.
Controller:
'use strict';
/**
* #ngdoc function
* #name lunchHubApp.controller:AnnouncementsCtrl
* #description
* # AnnouncementsCtrl
* Controller of the lunchHubApp
*/
angular.module('lunchHubApp')
.controller('AnnouncementsCtrl', ['$scope', 'Announcement', function ($scope, Announcement) {
$scope.announcements = [];
$scope.save = function() {
// This next line is the part I'm finding hard to test.
new Announcement($scope.announcement).create().then(function(announcement) {
$scope.foo = 'bar'
});
};
}]);
Test:
'use strict';
describe('AnnouncementsCtrl', function() {
beforeEach(function() {
module('lunchHubApp', 'ng-token-auth')
});
it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements).toEqual([]);
}));
describe('save', function() {
it('works', inject(function($controller, $rootScope, _$httpBackend_) {
var $httpBackend = _$httpBackend_;
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements.length).toBe(0);
var announcement = {
restaurantName: 'Bangkok Taste',
userId: 1
};
scope.announcement = announcement;
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
scope.$digest();
expect(scope.foo).toEqual('bar');
}));
});
});
Update: here's the way I ended up modifying my controller test. The following passes and has been refactored from the original.
'use strict';
describe('AnnouncementsCtrl', function() {
var $httpBackend,
announcement,
scope,
ctrl;
beforeEach(function() {
module('lunchHubApp');
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $injector.get('$rootScope').$new();
ctrl = $injector.get('$controller')('AnnouncementsCtrl', { $scope: scope });
announcement = { restaurantName: 'Bangkok Taste' };
scope.announcement = { restaurantName: 'Jason\'s Pizza' };
$httpBackend.expect('GET', '/api/announcements').respond([announcement]);
});
});
it('sets scope.announcements to an empty array', function() {
expect(scope.announcements).toEqual([]);
});
it('grabs a list of announcements', function() {
expect(scope.announcements.length).toBe(0);
$httpBackend.flush();
expect(scope.announcements.length).toBe(1);
});
describe('save', function() {
beforeEach(function() {
$httpBackend.expect('POST', '/api/announcements').respond(200, { restaurantName: 'Foo' });
scope.save();
$httpBackend.flush();
});
it('adds an announcement', function() {
expect(scope.announcements.length).toBe(2);
});
it('clears the restaurant name', function() {
expect(scope.announcement.restaurantName).toEqual('');
});
});
});
I think what you're doing is good. Since the Angular resources are factories using the $http service in a restful way, you should use the expect of the $httpBackend just as you did.
One thing that you miss however is that you need to make sure your promise is resolved. But write async tests can be tricky in some cases. To do so, you have to use the flush() method of $httpBackend to force your test to be synchronous.
After the flush, you can make your expect normally. Also you might have to move your expectPOST before your $rootScope.$new() statement.
You can go with a change like this, I don't think the $digest() is necessary:
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
$httpBackend.flush();
expect(scope.foo).toEqual('bar');
The tests you've started writing seem to be testing not just AnnouncementsCtrl, but the Announcements service/factory as well. The signs of this in this case are
You're not mocking the Announcements service/factory / not stubbing any of its methods.
There is no code in the AnnouncementsCtrl regarding making http requests, and yet you're using $httpBackend.expect(... in the tests for them.
The success/failure of the tests that claim to test AnnouncementsCtrl will succeed or fail depending on code in the Announcements service/factory.
This goes against what unit tests are usually used for: testing each component in isolation. Keeping the focus of this answer on testing the success callback passed to the then method of the promise returned by create, my suggestion is to mock the Announcements service/factory, so its create method returns a promise that you can control in the test. This mock would be of the form:
var MockAnnouncement = null;
var deferred = null;
beforeEach(module(function($provide) {
MockAnnouncement = function MockAnnouncement() {
this.create = function() {
return deferred.promise;
};
};
$provide.value('Announcement', MockAnnouncement);
}));
You would then have to make sure that you create deferred object before each test:
beforeEach(inject(function($rootScope, $controller, $q) {
$scope = $rootScope.$new();
deferred = $q.defer(); // Used in MockAnnouncement
ctrl = $controller('AnnouncementsCtrl', {
$scope: $scope
});
}));
This deferred object is then resolved in the test:
it('calls create and on success sets $scope.foo="bar"', function() {
$scope.save();
deferred.resolve();
$scope.$apply();
expect($scope.foo).toBe('bar');
});
A slightly extended version of this, testing a few other behaviours of the controller as well, can be seen at http://plnkr.co/edit/v1bCfmSPmmjBoq3pfDsk

jasmine angularjs testing - Argument 'PhoneListCtrl' is not a function, got undefined

When running an angularjs + Jasmine + Karma test, I got following error:
My test script is:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', { $scope: scope });
expect(scope.phones.length).toBe(3);
}));
});
});
This code is just a copy from official AngularJS tutorial here:
http://code.angularjs.org/1.2.0-rc.3/docs/tutorial/step_02
Here is part of my karma.conf.js file:
// list of files / patterns to load in the browser
files: [
'js/bower_components/angular/angular.js',
'js/bower_components/angular/ngular-mocks.js',
'js/app/controllers.js',
'test/unit/*.js'
],
The error is PhoneListCtrl not define, but I beleive it is defined and loaded in the above code. What do you think is the problem? Thanks!
Module initialization part is missing in your unit test. You should call module('phonecatApp') before you first time call inject(). Your unit test code in this case should look like:
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
beforeEach(function() {
module('phonecatApp'); // <= initialize module that should be tested
});
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', { $scope: scope });
expect(scope.phones.length).toBe(3);
}));
});
});
where phonecatApp is the name of the module where you defined your PhoneListCtrl controller.
Also tutorial you are using is outdated, it is for unstable version of Angular (1.2.0-rc.3). Here is an updated version of the same tutorial for the latest version of Angular: http://docs.angularjs.org/tutorial/step_02
this works for me
describe('addCatControllerTest', function() {
describe('addCatController', function(){
beforeEach(function() {
module('app');
});
beforeEach(inject(function($controller, $rootScope){
$scope = $rootScope.$new();
}));
it('Add Cat Controller test', inject(function($controller) {
var scope = {},
ctrl = $controller('addCatController', { $scope: scope });
expect(scope.title).toBe('Add Cat');
}));
});
});

Resources