I am trying to test my AngularJs code.
My controller is shown here:
'use strict';
(function() {
angular.module('myApp')
.controller('TestController', function($scope, $http, Auth, myService, $state) {
try{
$scope.testVar = "Hello";
}catch(e){
console.log(e);
}
});
});
My test code is below:
'use strict';
describe('Controller: TestController', function () {
beforeEach(module('myApp'));
var TestController, scope;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
TestController = $controller('TestController', {
$scope: scope
});
}));
it('should testVar to be defined', function () {
expect(scope).toBeDefined();
expect(scope.testVar).toBeDefined();
});
});
When I run it, it failed test with
Expected undefined to be defined
I don't know what I am doing wrong ...
I already checked many post on SO but didn't get it resolved yet.
Possibly issue is related to your variable declaration. You have declared ManualBookingController, but then you use TestController for the $controller line. Change ManualBookingController to TestController, see if that works.
I took your code almost as is, put it in a fiddle, test passes just fine. What I think might be missing in your case is you have some dependencies that are present in your controller but not mocked while creating the mock controller using $controller in your test suite. I removed them from your original controller (for now) and it worked fine.
And in order to mock $state (provider) and other services, you can see this same example fiddle passing with dependencies injected and read about underscore wrapping.
Also, keep in mind that sharing exact details on your question is very important. When I ran your test suite with your exact code, it first gave me following error:
Error: [$injector:unpr] Unknown provider: $stateProvider <- $state http://errors.angularjs.org/1.2.9/$injector/unpr?p0=%24stateProvider%20%3C-%20%24state
..which is not mentioned in your question at all. You mentioned only the next error Expected undefined to be defined which was very common and less-useful to imagine what's going on.
Related
I'm defining some setup code in the config function of an Angular module that I want to unit test. It is unclear to me how I should do this. Below is a simplified testcase that shows how I'm getting stuck:
'use strict';
angular.module('myModule', []).config(['$http', '$log', function($http, $log) {
$http.get('/api/getkey').then(function success(response) {
$log.log(response.data);
});
}]);
describe('myModule', function() {
it('logs a key obtained from XHR', inject(function($httpBackend) {
$httpBackend.expectGET('/api/getkey').respond(200, '12345');
angular.module('myModule');
$httpBackend.flush();
}));
});
This is clearly not the right way because I get the following error:
Error: No pending request to flush !
A complete, ready-to-run Angular project with the above testing code can be found on GitHub. If you know what to do with this scenario, please answer here on Stack Overflow. Bonus points if you also submit a pull request to the GitHub repo.
Use run instead of config if your initialization requires services to be injected. The config function can only receive providers and constants as parameters, not instantiated services like $http (relevant docs).
angular.module('myModule', []).run(['$http', '$log', function($http, $log) {
...
}]);
Initialize your module for testing
beforeEach(module('myModule'));
it('logs a key obtained from XHR', inject(function($httpBackend) {
$httpBackend.expectGET('/api/getkey').respond(200, '12345');
$httpBackend.flush();
}));
So the full working version looks like
'use strict';
angular.module('myModule', []).run(['$http', '$log', function($http, $log) {
$http.get('/api/getkey').then(function success(response) {
$log.log(response.data);
});
}]);
describe('myModule', function() {
beforeEach(module('myModule'));
it('logs a key obtained from XHR', inject(function($httpBackend) {
$httpBackend.expectGET('/api/getkey').respond(200, '12345');
$httpBackend.flush();
}));
});
Also, here's an example of testing the config block to check that a method on a provider was called: https://medium.com/#a_eife/testing-config-and-run-blocks-in-angularjs-1809bd52977e#71e0
mzulch is right to point out that services cannot be injected in an angular.module(...).config block. He also provides the right solution for the scenario where you actually need to use services in module initialization code: use the .run block instead of the .config block. His answer works perfectly for this scenario.
The question of how to write a unit test for the .config block remains. Let's adapt the naieve code from my question to a scenario where .config is actually warranted. The following snippet injects a provider dependency instead of a service dependency:
angular.module('myModule', []).config(['$httpProvider', function($httpProvider) {
$httpProvider.useApplyAsync(true);
}]);
describe('myModule', function() {
it('configures the $http service to combine response processing via $applyAsync', inject(function($httpProvider) {
angular.module('myModule');
expect($httpProvider.useApplyAsync()).toBeTruthy();
}));
});
This time, the implementation of 'myModule' is correct. The unit test however, which is analogous to the attempt in my question, is still incorrect. Now Karma gives me the following error:
Error: [$injector:unpr] Unknown provider: $httpProviderProvider <- $httpProvider
This cryptical error is coming from the inject which is passed as the second argument to the it. Note how Provider is being stuttered. This is caused by the fact that inject is looking for the provider for $httpProvider. A "meta provider", as we may call it. Such things don't exist in the Angular framework, but inject is trying it anyway because it expects you to only ask for service dependencies. Services do have providers, for example, $http has $httpProvider.
So inject (full name: angular.mock.inject, here available globally) is not the right way to get hold of $httpProvider in the testcase. The right way is to define an anonymous module configuration function using module (angular.mock.module) which closes over a variable in which we can capture the provider. This works because providers can be injected at configuration time (see the link at the bottom of mzulch's answer as well as my own answer to my other question for details on configuration time vs run time). It looks like this:
var $httpProvider;
beforeEach(function() {
module(function(_$httpProvider_) {
// this is a .config function
$httpProvider = _$httpProvider_;
});
// after this I can use inject() to make the magic happen
});
Another mistake in my naieve testcase is that I'm trying to execute 'myModule's configuration steps by calling angular.module('myModule'). For testcase purposes, I should be using the global module (angular.mock.module) instead, and the wisest place to do so is in the beforeEach fixture. In conclusion, the following code does the job:
describe('myModule', function() {
var $httpProvider;
beforeEach(function() {
module(function(_$httpProvider_) {
$httpProvider = _$httpProvider_;
});
module('myModule');
});
it('configures the $http service to combine response processing via $applyAsync', function() {
inject(); // enforces all the module config steps
expect($httpProvider.useApplyAsync()).toBeTruthy();
});
});
I opted to put the inject() at the start of my testcase, but I could also put it at the end of the beforeEach. The advantage of the latter approach would be that I can write the call to inject in one place and not need to repeat it in every testcase. The advantage of the approach actually taken here is that more modules can be added to the injector in later beforeEaches or even in individual testcases.
I pushed this alternative solution to a new branch on GitHub.
I have been looking around online, but seems, no one really injecting the actual dependencies into a unit test for Angularjs using Jasmine and Karma.
I think there is definitely a separation of concern for the testing process, but I also would like to know how it integrated well with current dependencies in use... so just in case a dependencies is not working well with my component, I will be aware of it!
So, I wonder how can I inject the actual dependencies? So far I found online articles are all about mocking it with a fake one... But I want to use the actual one. Right now, when I enter karma start I am getting a error of Error: [$injector:unpr] Unknown provider: _Provider <- _ <-MyService
I inject services in forEach block like this
beforeEach(angular.mock.inject(function(_MyService_) {
I wonder if its because I am not using the fake service?
describe('MyCtrl', function() {
//Data Exposure Prep
var $controller;
var $rootScope;
var $scope;
var controller;
var MyService;
dd1 = {
itinerary: globalMockData.d1,//I stored globalMockData somewhere else
};
beforeEach(angular.mock.module('myapp'));
beforeEach(angular.mock.inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$rootScope = _$rootScope_;
$controller = _$controller_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $controller('MyCtrl', { $scope: $scope }, dd1);
}));
//BASIC INFO
describe('should receive sth', function() {
it('finds sth', function() {
expect(controller.answer).toBeDefined();
});
});
});
It really depend on your Controlller implementation.
If you're using scope, you'll probably want to test things on controller's scope, not on the controller itself.
Testing controller with $scope
function MyController($scope, MyService) {
$scope.greetUpperCase = function(name) {
return MyService.greet(name).toUpperCase();
}
}
Basically, when you’re working with $scope, you don’t really care about the controller itself, since it’s doing everything on it’s scope.
So, we will need to:
inject $rootScope
create a new scope
inject it to a new controller
test the $scope
it('test greetUpperCase()', function() {
var myScope = $rootScope.$new();
$controller('MyController', {
$scope: myScope
});
expect(myScope .greetUpperCase('bob')).toBe('HELLO BOB');
});
https://jsfiddle.net/ronapelbaum/pkhaxmdg/
I have added some logic for authentication in my angular app and the initial the call to the service wrapping the webapi is executed in the app.run function, like this:
myApp.run(function ($rootScope, $location, myService) {
myService.getCurrentUser().then(function (promise) {
//reroute to either access denied or the start page when access is verified.
});
// register listener to watch route changes
$rootScope.$on("$routeChangeStart", function (event, next, current) {
//check access
});
});
After this all my unit tests broke, since I don't know how to inject the mocked version of myService before creating the app module. I've factored out the service and the mocked service in a seperate module so I can create it before creating the actual app. Like this:
angular.mock.module('ServiceModule');
angular.mock.module('EArkivApp', function ($provide, myMockedService) {
$provide.value('myService', myMockedService);
});
This is not working however, it complains that "myMockedService" (which is part of the ServiceModule) is an unkown provider. Do you have any good suggestions of how I should solve this?
Could you provide us for a jsfiddle?
Meanwhile, if you are using Karma then you can use something like this:
/*global describe, beforeEach, module, inject, it, expect*/
describe('Controller: MyCtrl', function () {
'use strict';
// load the controller's module
beforeEach(module('myApp'));
var MyCtrl,
scope,
modalInstance;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, myMockedService) {
scope = $rootScope.$new();
modalInstance = _$modal_.open({
templateUrl: 'views/my-template.html'
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
$myMockedService: myMockedService
});
}));
it('should be true', function () {
expect(true).toBe(true);
});
});
I learned this by using Yeoman scaffolding, by the way :)
I ran into issues writing jasmine tests for an AngularJS application using angular ui-router. My services and app get initialized properly in the test, but the controllers do not start up properly. I've taken the application in question out of the equation and reduced the problem to a simple one controller example that exhibits the same behavior. Here's the actual test code:
describe('Test', function() {
var async = new AsyncSpec(this);
var scope = {};
beforeEach(angular.mock.module('TestApp'));
beforeEach(angular.mock.inject(function($rootScope, $state, $templateCache) {
scope.$rootScope = $rootScope;
scope.$state = $state;
$templateCache.put('start.html', '<div class="start"></div>');
}));
async.it('Test that TestCtrl is initialized', function(done) {
scope.$rootScope.status = { done: false };
scope.$rootScope.$on('$stateChangeSuccess', function(event, state, params) {
expect(scope.$rootScope.status.done).toBe(true);
done();
});
scope.$state.transitionTo('start', {}, { notify: true });
scope.$rootScope.$apply();
});
});
Here's the complete runnable test
The application gets initialized correctly, the ui router is able to transition the application to the correct state, but the controller does not get initialized. I need the router to initialize the controllers as the router passes critical configuration to them. I want to avoid duplicating that configuration in the tests.
I must be missing something, but what? I appreciate any and all input, thanks!
You need to use the $controller service to instantiate your controller in your tests and pass it your scope. For example...
ctrl = $controller('TestCtrl', {$scope: scope});
Notice that I also moved the declaration of $rootScope.done to the TestCtrl to prevent an error about $rootScope.done being undefined. Here's the fiddle...
http://jsfiddle.net/C8QtB/3/
While running grunt test on a Yeoman (1.0RC1) scaffolded Angular (1.0.7) app, I'm getting the following error:
TypeError: 'undefined' is not a function (evaluating '$scope.$parent.userLoggedIn(true)')
userLoggedIn() is within a parent controller index.js. The function itself runs fine within the angular app.
This error doesn't occur on my other $scope.$parent boolean or strings variables in the controller, so it's related directly to calling functions within a parent.
I'm thinking that I'm either using $scope.$parent the wrong way or that I need to define my index.js controller in the test, but Karma testing documentation is sporadic, so it's hard to know.
EDIT: Here is the controller:
'use strict';
angular.module('uiApp')
.controller('LookupCtrl', ['$scope', function ($scope) {
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
$scope.$parent.userLoggedIn(true);
}]);
Here is the test:
'use strict';
describe('Controller: LookupCtrl', function () {
// load the controller's module
beforeEach(module('uiApp'));
var LookupCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
LookupCtrl = $controller('LookupCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.awesomeThings.length).toBe(3);
});
});
(Yes I know I'm just using the default awesomeThings test for now. I'm new to Angular testing).
You're giving the controller a rootScope, which doesn't have $parent (it's the root).
Change your controller code to call it correctly (using the prototype chain) and you'll be fine by just passing {my: props} as an object.