We just started implementing jasmine tests in our AngularJS project and I have a question:
We want to test this controller function:
$scope.deleteClick = function () {
$scope.processing = true;
peopleNotesSrv.deleteNote($scope.currentOperator.operatorId, $scope.noteId, $scope.deleteSuccessCallback, $scope.deleteErrorCallback);
};
We wrote this following test:
it('deleteClick should pass proper parameters to peopleNoteSrv', function () {
$controllerConstructor('PeopleNoteEditCtrl', { $scope: $scope });
$scope.noteId = 5;
expect(function () { $scope.deleteClick(); }).not.toThrow();
});
This test makes sure that when we call the $scope.deleteClick() function that $scope.processing is set to true and that the call to peopleNotesSrv doesn't throw any errors because of invalid arguments. We are testing the two callback functions in separate tests.
Should we be testing that the peopleNotesSrv.deleteNote function was called so the test is more explicit? The way this test is written right now it doesn't really tell someone what the deleteClick() function does under the hood and that seems to be incorrect.
Ask yourself what you'd do if you had developed it using TDD. It pretty much goes the direction Sam pointed out, but here are some examples:
Controller Tests
start writing a test which would expect a deleteClick to exist.
Expect deleteClick to setup the loading state (check for processing = true)
Test whether a service is injected into the controller (peopleNotesSrv)
Check whether deleteClick calls the service (as already mentioned via spies)
Verify that $scope.noteId and the other $scope.params are present and set
This is as far as it relates to the Controller. All the criteria whether it fails or throws errors etc. should be tested in a Service.spec. Since I don't know your service in detail here some examples
Service Tests
Ensure deleteNote exists
Check what happens if wrong number of arguments (less or more) are supplied
Make some positive tests (like your noteId = 5)
Make some negative tests
Ensure callbacks are properly called
... and so on.
Testing for validity in controllers doesn't make a lot of sense because than you'd need to do it for every Controller you have out there. By isolating the Service as a separate Unit of Test and ensure that it fulfills all the requirements you can just use it without testing. It's kinda the same as you never would test jQuery features or in case of Angular jQLite, since you simply expect them to do what they should :)
EDIT:
Make controller tests fail on service call
Pretty easy lets take this example. First we create our Service Test to ensure that the call fails if not the proper number of arguments is supplied:
describe('Service: peopleNoteSrv', function () {
// load the service's module
beforeEach(module('angularControllerServicecallApp'));
// instantiate service
var peopleNoteSrv;
beforeEach(inject(function (_peopleNoteSrv_) {
peopleNoteSrv = _peopleNoteSrv_;
}));
it('should throw error on false number of arguments', function () {
expect(function() { peopleNoteSrv.deleteNote('justOneParameter'); }).toThrow();
});
});
Now to ensure that the test passes lets create the error throwing part in our service method
angular.module('angularControllerServicecallApp')
.service('peopleNoteSrv', function peopleNoteSrv() {
this.deleteNote = function(param1, param2, param3) {
if(arguments.length !== 3)
throw Error('Invalid number of arguments supplied');
return "OK";
};
});
Now lets create 2 demo controllers, FirstCtrl will do it properly, but SecondCtrl should fail
angular.module('angularControllerServicecallApp')
.controller('FirstCtrl', function ($scope, peopleNoteSrv) {
$scope.doIt = function() {
return peopleNoteSrv.deleteNote('param1', 'param2', 'param3');
}
});
angular.module('angularControllerServicecallApp')
.controller('SecondCtrl', function ($scope, peopleNoteSrv) {
$scope.doIt = function() {
return peopleNoteSrv.deleteNote('onlyOneParameter');
}
});
And both controller as a demo have following test:
it('should call Service properly', function () {
expect(scope.doIt()).toBe("OK");
});
Karma now spits out something like this:
Error: Invalid number of arguments supplied
at [PATH]/app/scripts/services/peoplenotesrv.js:15
at [PATH]/app/scripts/controllers/second.js:13
at [PATH]/test/spec/controllers/second.js:20
Thus you exactly know that you missed to update SecondCtrl. Of course this should work for any of your tests consuming the Service method.
Hope that's what you meant.
I think the answer is that it depends.
There are two cases:
1 - You also have a suite of tests for the peopleNotesSrv service.
In this case I would leave this test as-is or check a few more things around the specific functionality of $scope.deleteClick(), such as if there are any watchers on $scope.processing that do anything specific regarding a .deleteClick() call.
2 - You do not have any tests for all the possible functionality for the peopleNotesSrv service.
In this case I would write a more explicit test that does check that the .deleteNote() actually performed it's job.
In my opinion you should really build tests up and try to not test the same thing in more than one place, as this adds extra work and could produce holes in the tests if you think, "Well I can just test this specific case when it gets called from a specific function that calls it."
What if you ever want to reuse that deletNote() as part of a bigger function in a different place?Then you need to write another test for the same code because it is being called from a different function.
So I would aim for case 1, this way you can write all your tests for that service and then trust that those tests cover the rest of this particular test. If you throw errors on bad input or for failures to actually delete a note, you should trust that other code to test what it was designed to test. This will greatly speed up your test-writing time and increase the chance that your tests cover all the cases. It also keeps all the tests for that service in the same place in your test code.
I think also a good question to start with is what kind of test is this? Unit Test or End-to-End test?
I was assuming it was a Unit Test for my answer, if it was an End-to-End test, then you might want to keep following the function calls to verify everything is happening as you expect.
Here are some links on Unit Tests, End-to-End tests, and a pretty good article about both and Angular.
What's the difference between unit, functional, acceptance, and integration tests? (End-to-End tests can also be called Integration test)
http://www.sitepoint.com/unit-and-e2e-testing-in-angularjs/
Related
I am starting out in JS unit testing and having a hard time getting my head around how to create meaningful tests with Jasmine spies.
it('should take an array of shopping items', function() {
spyOn(checkObj, 'getTotal');
checkObj.getTotal([1, 2, 3]);
expect(checkObj.getTotal).toHaveBeenCalledWith(jasmine.any(Array));
});
Using the above excerpt of test I created as an example I cant see how this is a useful test as my call to getTotal is hard coded inside the spec. But at the same time I would like to ensure the param passed is an array and not some other type...its the hard coding that I am sure is wrong somehow.
Could someone offer a bit of guidance on how I should think/approach this type of testing scenario
Well, spies are useful for a bit different scenario. Much depends on how you yourself define the scope of your unit test as well. If you do the minimal possible unit (i.e. method) then lets imagine the following:
var x = function() { }
x.prototype.f1 = function() {
//do something
},
x.prototype.f2 = function(){
// do something else
this.f1();
}
Now, in your unit test for f2 you are not interested in how f1 works inside. so, you make a spy on it:
var a = new x();
a.f1 = jasmine.createSpy("spy-on-f1");
expect(a.f1).not.toHaveBeenCalled();
a.f2();
expect(a.f1).toHaveBeenCalled();
For example, for angularjs applications, I often mock whole services with spies, just to isolate the algorithm in testing.
As a bonus, you can actually replace the real call with some fake function like this:
a.f1 = jasmine.createSpy("fake-spy").and.callFake(function(){
// do something predictible or return global variable that can be set externaly
});
I have a set of tests that are spread across 2 modules.
The first module has tests and for it's dependencies i declare mocks to test it without any influence from it's dependent module, like so:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
The second module has a series of tests and all of them pass when i run only them.
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
Both tests when run with f(Running only them) works, but when i run the whole test suit i get errors, specially regarding module declaration or $httpBackend.
How can i make jasmine run each test as if they were the only tests?
It seems i am messing with the angular/modules/$httpBackEnd on each test and the changes are being propagated when it starts a new test.
Update 1
I have a jsFiddle representing the issue .
The structure of the problem is :
Some test is ran with a mock dependant module
Later another test wants to test the actual mocked module
Since the first moldule was already loaded we can't overwritte it and the test fails.
On the JSFiddle the error about $httpBackend without nothing to flush is because the request for the expectedGet is never hit, and it's never hit because of the previously loaded empty module
It's important to notice that the ORDER of the tests is the only thing relevant to failing as in this JSFiddle with the same tests they pass.
I could of course make a tests order and bypass this but i am aiming to find a way to do the tests with isolation without worrying about other tests side effects.
The problem you are experiencing is due to the nature of how the $compileProvider handles a new directive being registered with a pre-existing name.
In short; You are not overriding your old directive, you are creating a secondary one with the same name. As such, the original implementation runs and tries to grab baz.html and $httpBackend throws as you have not set up an expectation for that call.
See this updated fiddle that did two changes from your original fiddle.
Do not inject the parentModule to your childModule spec. That line is not needed and it is part of the reason you are seeing these errors. Oh and angular.module is evil in the land of tests. Try to not use it.
Decorate the original directive if you wish to roll with the same name as the original one, or name it something else. I've opted for naming it something else in the fiddle, but I have supplied code at the end of my answer to show the decorator way.
Here's a screenshot of what happens in the following scenario:
Module A registers a directive called baz.
Module B depends on module A.
Module B registers a directive called baz.
As you can probably imagine, in order for the module system to not insta-gib itself by letting people overwrite eachothers directives - the $compileProvider will simply register another directive with the same name. And both will run.
Take this ng-click example or this article outlining how we can leverage multiple directives with the same name.
See the attached screenshot below for what your situation looks like.
The code on lines 71 to 73 is where you could implement solution #2 that I mentioned in the start of my answer.
Decorating baz
In your beforeEach for testModule, replace the following:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
With this:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
What about the call to angular.module('parent', [])?
You should not call angular.module('name', []) in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
Only ever use .mock.module or window.module, as otherwise you will kill your upcoming specs that relate to the specified module, as you have effectively killed the module definition for the rest of the spec run.
Furthermore, the directive definition of baz from parentModule will automatically be available in your testModule spec due to the following:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
So, even if we kill the angular.module('parent', []) call in your spec, the original baz definition will still be loaded.
As such, the HTTP request flies off due to the nature of $compileProvider supporting multiple directives with the same name, and that's the reason your spec suite is failing.
Also, as a last note; You are configuring undefined modules in your beforeEach blocks. If the goal is to configure the module of your test, you are in the wrong.
The syntax is as follows:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
This can be seen in the screenshot I posted. The $$moduleName property of your mocked baz definition is undefined, whereas I am assuming you would want that to be child.
The following snippet of code is from angular's documentation. What is the it() function here doing (I'm assuming it has some conventional meaning because otherwise no context seems to be given for its meaning)? I don't see any reference to it on angular's site. It's also hard to google due to it's name. The context is with regards to code testing.
it('should say hello', function() {
var scopeMock = {};
var cntl = new MyController(scopeMock);
// Assert that username is pre-filled
expect(scopeMock.username).toEqual('World');
// Assert that we read new username and greet
scopeMock.username = 'angular';
scopeMock.sayHello();
expect(scopeMock.greeting).toEqual('Hello angular!');
});
The it() function is defined by the jasmine testing framework, it is not part of angular per se. You'll see it in angular's documentation because they are encouraging you (for good reason) to get in the habit of writing tests for your code, and demonstrating how the code will work in a test.
The it() function defines a jasmine test. It is so named because its name makes reading tests almost like reading English. The second argument to the it() function is itself a function, that when executed will probably run some number of expect() functions. expect() functions are used to actually test the things you "expect" to be true.
Read more about jasmine testing on the jasmine framework's website: http://jasmine.github.io/
it is related to tests with jasmine framework, you can find more information here:
http://jasmine.github.io/
https://docs.angularjs.org/guide/unit-testing
I'm new to unit testing, and after reading the docs I'm still confused on how to do checks on asynchronous methods, as far as i understand I will need to use runs() and wait(). However, instead of doing that, I could use spyOn to check if the method has been called instead.
this is the code i want to test
$scope.createMedicalServices = function(){
if($scope.newTreatment.async_selected_treatment.id == null){
$scope.newTreatment.description = $scope.newTreatment.description_no_id;
}
else{
$scope.newTreatment.description = $scope.newTreatment.description;
}
$scope.newTreatment.no_cpt_code = $scope.newTreatment.no_cpt_code;
$scope.newTreatment.cash_price = $scope.newTreatment.cash_price_input;
$scope.newTreatment.average_price = $scope.newTreatment.average_price_input;
$scope.newTreatment.service = $scope.newTreatment.async_selected_treatment.service;
var returnPromiseObject;
if ($scope.newTreatment.no_cpt_code) {
returnPromiseObject = ProviderMedicalService.createNew($scope.newTreatment);
}
else if($scope.newTreatment.async_selected_treatment.id == null){
returnPromiseObject = ProviderMedicalService.createNewCPT($scope.newTreatment);
}
else{
returnPromiseObject = ProviderMedicalService.createExisting($scope.newTreatment);
}
returnPromiseObject.then(
function (value){
$scope.newTreatment.id = value.id;
$scope.provider_medical_services.push($scope.newTreatment);
},
function (error){
console.log(error);
});
};
$scope.searchTreatments = function(value){
return Treatment.find(value).then(function(values){
return values;
});
};
and this is what i wrote so far, but its not much as i have no idea how to test it.
it('search treament should have been called and return a value'),function(){
scope.searchTreatments();
}
it('it should create medical service after submitting the form'),function(){
scope.createMedicalServices();
runs
}
Basically, I am content if the method just gets called properly, as the createMedicalService function is actually executed the moment the user clicks on the submit form, as its part of ng-submit on the html file.
Help and suggestions greatly appreciated
Unit testing can be a little confusing at first. There are a few principles that help with unit testing.
Singleness of Purpose. A component should just do one thing. Consider breaking your code into smaller parts. Move logic out of controllers into factories or services. These can be tested individually.
Inversion of Control. Angularjs does a great job at providing IoC built in. Pass in your dependencies as stubs, fakes or mocks into your unit.
Only test the interface. It is a bad practice to test the internal call structure of a unit. This ends up not testing the functionality, but rather the implimentation. Instead think about this example.
You have a controller that gets a rest service passed in called nameService. This controller has a method called submit(first, last) that when called, should called the method post(args) on the nameService.
This would allow you to create a stub of the nameService, create a spy on the post() method and pass in the stub to the controller we are testing. Then you can simply call submit() on the controller and test that it called the service.
In essence, you test that if you input something into your unit, your unit will output it as expected.
After a brief romance with the revealing module pattern I've come to realise a set-back when it comes to unit-testing modules. I cannot however decide if it is my approach to testing a module or whether there is some form of work-around.
Consider the following code:
var myWonderfulModule = (function () {
function publicMethodA (condition) {
if(condition === 'b') {
publicMethodB();
}
}
function publicMethodB () {
// ...
}
return {
methodA : publicMethodA,
methodB : publicMethodB
}
}());
If I wanted to test (using Jasmine) the various paths leading through publicMethodA to publicMethodB. I might write a small test like so:
it("should make a call to publicMethodB when condition is 'b'", function() {
spyOn(myWonderfulModule , 'publicMethodB');
myWonderfulModule.publicMethodA('b');
expect(myWonderfulModule.publicMethodB).toHaveBeenCalled();
});
If I understand correctly, there's a copy of publicMethodB within the closure that cannot be changed. Even if I change myWonderfulModule.publicMethodB afterwards:
myWonderfulModule.publicMethodB = undefined;
calling myWonderfulModule.publicMethodA will still run the original version of B.
The example above is of course simplified but there are plenty of scenarios I can think of where it would be convenient to unit test conditional paths through a method.
Is this a limitation of the revealing module pattern or simply a misuse of unit testing? If not what work-arounds are available to me? I'm considering moving to something like RequireJS or reverting back to non-modular code.
Any advice appreciated!
You cant test the intern methodes of a closure. And you also shouldn't spy on it. Think about about your module as a black box. You put something in and you get something out. All you should test is that the thing you get out of your module is the one that you expect.
Spying on methodes in your module makes not much sense. Think about it. You spy on it, the test passes. Now you change the functionality so it creates a bug, the test still passes cause the function is still called but you never mention the bug. If you just test the thing that cames out you dont need to spy on internal methodes cause, that they are called is implicite when the outcome of the module is what you expect.
So in your case there is no thing that goes in and nothing comes out. This makes not much sense but I believe that your module interacts with DOM or makes an ajax call. This are things that you can test (DOM) or you should spy on (ajax).
You should also make you self familiar with Inversion of Control and Dependency Injection. These are patterns that will make your modules much more easier to test.
If you use the keyword "this" when you call publicMethodB() from publicMethodA() it will work. For example:
var myWonderfulModule = (function () {
function publicMethodA (condition) {
if(condition === 'b') {
this.publicMethodB();
}
}
function publicMethodB () {
// ...
}
return {
methodA : publicMethodA,
methodB : publicMethodB
}
}());