Tearing my hair out on this one.
I have the following module/controller I wish to test
angular
.module('monitor.tableLord.controller', ['monitor.wunderTable'])
.controller('TableLordController', TableLordController);
TableLordController.$inject = ['$scope', 'historyState'];
function TableLordController($scope, historyState) {
....some code....
}
The module monitor.wunderTable contains a directive that should be loaded before the controller, but the controller I want to test does not actually depend on monitor.wunderTable. monitor.wunderTable does however have a LOT of other dependencies....
My testfile:
describe('TableLordController', function() {
var $scope, historyState;
beforeEach(function() {
angular.module('monitor.wunderTable', []);
module('monitor.tableLord.controller');
});
beforeEach(angular.mock.inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('TableLordController', {$scope: $scope, historyState: {}});
}));
it('loads', function() {
$scope.$digest();
});
});
For some reason (I didn't think this should be possible), my mock version of monitor.wunderTable is interfering with the tests i have for this module. Every test of the controller defined in this module now fails with: "Argument 'WunderTableController' is not a function, got undefined".
I case it is relevant, here is my the definition of monitor.wunderTable:
angular
.module('monitor.wunderTable', [
'ui.grid',
'ui.grid.infiniteScroll',
'ui.grid.autoResize',
'monitor.wunderTable.service'
])
.controller('WunderTableController', WunderTableController)
.directive('wunderTable', wunderTable);
function wunderTable(){...}
WunderTableController.$inject = [];
function WunderTableController(...){...}
Edit: Posts suggesting that I remove the module dependency (as it is not strictly needed) will not be accepted as correct answer (and possibly downwoted).
Your confusion comes from misunderstanding how modules work in Angular. Modules are stored inside angular. Once they are overriden, they are overriden for the current test run, not for the current spec. Module mocking isn't supported by ngMock (it would require some substantial changes in Angular core) and beforeEach won't help anything.
Unless you want to run test suites in separate runs, the solution is to backup the module before mocking it. In some cases angular.extend and angular.copy are unable to handle complex objects properly. Object.assign, jQuery.extend or node-extend may be better candidates. In the case of extends deep copy can also used if necessary.
So in one test suite it is
var moduleBackup = angular.module('monitor.wunderTable');
angular.module('monitor.wunderTable', []);
describe('TableLordController', function() {
...
And in another
Object.assign(angular.module('monitor.wunderTable'), moduleBackup);
describe('WunderTableController', function() {
...
The good thing about TDD is that it clearly indicates the flaws in app design and teaches the developer to write test-friendly code in immediate and ruthless manner. Module dependency implies that the components inside the module depend on the components from another one. If they doesn't or they are coupled too tightly, this can be considered a potential flaw and the subject for refactoring.
The fact that the solution is hack-ish and tends to break makes it unfit for testing.
TL;DR: Yes, you have to remove the module dependency.
Related
I am New to learn a Angular Javascript. Can anyone gave me the knowledge of Dependency Injection with Its Demo Example. So That I did learn from there. No Good and clear link I have found from Googling.
Dependency
injection is a design pattern that allows for the removal of hard-coded dependencies, thus making
it possible to remove or change them at run time.
In general, there are only three ways an object can get a hold of its dependencies:
We can create it internally to the dependent.
We can look it up or refer to it as a global variable.
We can pass it in where it’s needed.
With dependency injection, we’re tackling the third way.We dont follow the first 2 ways because a good programmer never dirty the global scope and it will be difficult for the isolation of the code.
This ability to modify dependencies at run time allows us to create isolated environments that are
ideal for testing. We can replace real objects in production environments with mocked ones for
testing environments.
For instance,let us consider this simple app that declares a single module and a single controller, like so:
angular.module('myApp', [])
.factory('greeter', function() {
return {
greet: function(msg) { alert(msg); }
}
})
.controller('MyController',
function($scope, greeter) {
$scope.sayHello = function() {
greeter.greet("Hello!");
};
});
At run time, when Angular instantiates the instance of our module, it looks up the greeter and simply
passes it in naturally.
Nowhere in the above example did we describe how to find the greeter; it simply works, as the
injector takes care of finding and loading it for us.
For further reference please visit Angularjs Modularization and Dependency injection which can help you get a better understanding.
I'm trying to compose some unit tests in Karma/Jasmine for a particular module in my project, destination-filters.
Module Decleration:
angular.module('destination-filter', ['ngSanitize']);
My tests fail unless I remove ngSanitize as a dependency. To my understanding that is because when the module is instantiated it will try and pull in that dependency but because in my spec.js file I haven't declared that module it is failing.
Spec File:
describe('Destination Filter Controller', function () {
// Set the variables
var $controller;
var mockNgSanitize;
beforeEach(module('destination-filter'));
beforeEach(function() {
module(function($provide) {
$provide.value('ngSanitize', mockNgSanitize);
});
});
beforeEach(inject(function (_$controller_) {
$controller = _$controller_('DestinationFilterController');
}));
it('should expect the controller to not be null', function() {
// Check the controller is set
expect($controller).not.toBeNull();
});
});
Previously, when mocking out services or functions, the $provide method has proven very useful but I'm not sure my use of it is correct here. I'm assuming $provide used in this way can't mock entire modules but rather services?
To clarify, if I remove the ...['ngSantize'])... from my module deceleration the tests instantiate correctly. The error I am receiving with it left in is Error: [$injector:modulerr] destination-filter
There are three options you could take for using ngSanitize in your tests:
inject the service into your test
stub a method call on ngSanitize
mock the entire ngSanitize service
The option you choose is really dependent on the use of ngSanitize in your working code (not your test code).
Whichever one you go for you need to make the service available in your test, there is no need for $provider (this covers option 1 and there is no need to do any more than this if you just want to make this available to your filter):
beforeEach(module('ngSanitize'));
beforeEach(inject(function(_ngSanitize_) { // the underscores are needed
mockNgSanitize = _ngSanitize_;
}));
Also, make sure that all js files are picked up and loaded by karma. You can define this in karma.conf.js by adding them to the files: property.
2. Stub a method on the service
I like stubs and find them very useful when writing tests. Your tests should only test one thing, in your case a filter. Stubs give you more control over your tests and allow you to isolate the thing under test.
Typically filters, controllers, anything call on lots of other things (services or factories like $http or ngSanitize).
Assuming that your filter is using ngSanitize's $sanitize to sanitize some html you could stub out that method to return sanitized html you have defined to test against your expectations:
// in a beforeEach
spyOn(mockNgSanitize, "$sanitize").and.returnValue('<some>sanitized<html>');
mockNgSanitized.$sanitize(yourDirtyHtml);
See the jasmine docs for more info.
You might have to play around with spying on the right service but this should work ok.
3. Mock the entire service
I don't think you want to go with this option because it will drive you insane figuring out what needs mocking plus mocks can create unrealistic expectations and also, not particularly useful for your use case. If you really want to have a go then something like the below is heading in the right direction (again see the jasmine docs)
beforeEach(function() {
mockNgSanitize = ('ngSanitize', ['linky', '$sanitize', '$sanitizeProvider'];
});
it('mocks the ngSanitize service', function() {
expect(mockNgSanitize.linky).toBeDefined();
});
NB: in all the code above make sure you continue to declare any variables up at the top of your describe block.
I'm having trouble understanding how to set up Jasmine to work with Angular so I can do testing. I'm following the instructions here under the heading titled "Testing a controller". According to the documentation, you should have your app & controller, defined like you normally would (this is pasted from the documenation):
angular.module('app', [])
.controller('PasswordController', function PasswordController($scope) {
//controller code goes here (removed for brevity)
});
and then you should have as your testing suite code, for example (pasted from the documentation as well).
describe('PasswordController', function() {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('PasswordController', { $scope: $scope });
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
But I'm terribly confused about a few things.
The documentation explains that you need to use angular-mocks to load in the controller, but in their example, they don't declare ngMocks as an app dependency (see the first block of code I pasted above).
It says that you can use angular.mock.inject to inject the controller into the current context. I loaded in the script http://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-mocks.js and now there is an angular.mock on the global scope, but it does not have an inject method. Furthermore, since the testing code runs outside of the controller, I don't understand how using the ngMocks dependency in the angular app helps with providing global methods for injecting controllers. The whole thing doesn't make sense to me.
Same goes for module. It says you can use it for the beforeEach(module('app'));, and that it's provide by angular-mocks, but angular.mock has no module method.
If someone could explain what I'm doing wrong I would very much appreciate it!
So I discovered that problem was that my script tag for angular-mocks was before my script tags for Jasmine when it really needs to go after. In the typical spirit of Angular "documentation", this was mentioned nowhere. After rearranging the script tags both module and inject were globally available methods.
So to answer my first question, you don't need to put ngMock in the dependencies. This answers questions 2 and 3 since module and inject are both now globally available.
So the scripts need to be placed in this order.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/boot.js"></script>
<!--angluar mocks script MUST go after the other declarations otherwise it won't add the inject and module methods to the scope -->
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-mocks.js"></script>
I'm certainly missing some fundamental point about the injector, but I fail to understand why exactly this
angular.module('app').config(function ($provide) {
...
});
and this
angular.module('app').config(function ($injector) {
$injector.invoke(function ($provide) { ... });
});
work as intended, while this
app.run(function($provide) {
...
});
will throw
Error: [$injector:unpr] Unknown provider: $provideProvider <- $provide
As follows from the above, config has some special relationship with providers, while run deals with instances, yet I'm unsure about the thing that makes config blocks so special.
As a consequence of that, is there no way to get to $provide outside config blocks, e.g. with angular.injector() (though it seems that it gets provider instances also)?
The question, besides mere curiosity, also has some practical considerations. In 1.4 all of $provide functions are exposed to module, but that's not true for 1.3.
The purpose of the config() function is to allow you to perform some global configuration that will affect the entire application - that includes services, directives, controllers, etc. Because of that, the config() block must run before anything else. But, you still need a way to perform the aforementioned configuration and make it available to the rest of the app. And the way to do that is by using providers.
What makes providers "special" is that they have two initialization parts, and one of them is directly related to the config() block. Take a look at the following code:
app.provider('myService', function() {
var self = {};
this.setSomeGlobalProperty = function(value) {
self.someGlobalProperty = value;
};
this.$get = function(someDependency) {
this.doSomething = function() {
console.log(self.someGlobalProperty);
};
};
});
app.config(function(myServiceProvider) {
myServiceProvider.setSomeGlobalProperty('foobar');
});
app.controller('MyCtrl', function(myService) {
myService.doSomething();
});
When you inject a provider into the config() function, you can access anything but the $get function (technically you can access the $get function, but calling it won't work). That way you can do whatever configuration you might need to do. That's the first initialization part. It's worth mentioning that even though our service is called myService, you need to use the suffix Provider here.
But when you inject the same provider into any other place, Angular calls the $get() function and injects whatever it returns. That's the second initialization part. In this case, the provider behaves just like an ordinary service.
Now about $provide and $injector. Since they are "configuration services", it makes sense to me that you can't access them outside the config() block. If you could, then you would be able to, say, create a factory after it had been used by another service.
Finally, I haven't played with v1.4 yet, so I have no idea why that behavior apparently has changed. If anyone knows why, please let me know and I'll update my answer.
After some Angular injector study I was able to give an exhaustive answer to my own question.
Essentially, $injector in config blocks and provider constructor functions and $injector everywhere else are two different services with the same name, which are defined on internal provider/instance cache explicitly, together with $provide (this one is being defined in provider cache, hence it can be injected in config only).
While generally not recommended because of probable race conditions, it is possible to expose internal services to instance cache and make config-specific $provide and $injector available for injection after config phase has ended:
app.config(function ($provide, $injector) {
$provide.value('$providerInjector', $injector);
$provide.value('$provide', $provide);
});
The possible applications are configuring service providers any time (if possible)
app.run(function ($providerInjector) {
var $compileProvider = $providerInjector.get('$compileProvider');
...
});
and defining new components at run-time
app.run(function ($provide) {
$provide.controller(...);
...
});
I've got the guts of a routing architecture in Angular that will dynamically download and inject Angular Controllers and Services ... the Controller part works fine, and I'm trying to download dependant services via $route's .resolve property.
Now, say if I have a factory declared in scope while the page starts up, it registers fine and Controllers that use it resolve fine, e.g:
myModule.factory('MyInjectedDep', function() {
return {};
});
....
MyController = function(MyInjectedDep)
But if I try and register that dependency at "run time" (for want of a better phrase), I get a Circular Dependency error. e.g:
$route.routes[routeItem.route] = {
resolve: {
MyInjectedDep: ['$injector', function($injector) {
// In real code I download/eval this via $http but same behavior occurs
myModule.factory('MyInjectedDep', function() {
return {};
});
}]
}
}
So when my Controller is then initiated:
MyController = function(MyInjectedDep)
I get a circular dependency error, but no dependency trace in the error message?
Error: Circular dependency:
Any ideas appreciated
The key is latching onto $provide at configuration time. If you grab $provide at configuration time and maintain a reference to it, you can use it to register your factory like:
$provide.factory.apply(null, ['MyInjectedDep', [function() {
return {};
]}]);
I have a provider/service designed to do this, adapted from some other samples on github: https://github.com/afterglowtech/angular-couchPotato .
It's primarily designed to load from AMD, but you could probably use it's registerXXX functions with $http, or at least copy the relevant portions of its code. Don't let the size of the repository fool you -- the actual provider/service is about one page of code https://github.com/afterglowtech/angular-couchPotato/blob/master/src/couchPotato.js .