April 01, 2014

Mocking a Confirmation Dialog in AngularJS Unit-Testing


What is more convenient then talking about the shallow gratification tests give us in the previous post, and now posting about testing examples. What can I say, you got me, I'm a child ;)

Anyhow, lets take the following case: User wishes to delete some book from his library, once he presses the delete button a confirmation dialog pops up to ask him if he is stupid or not, and once he approve that he is, the entity is being deleted.

Now, some of you may jump and shout that this is no unit! this is an automation test par excellence, but I would have to shut them up and say that yeah, this should also be tested in automation but there are unit aspects that need to be checked as well. let me explain-
You controller will probably contain 2 methods:
  • confirmBookDeletion - which pops the dialog
  • deleteBook - which actually deletes the book
In my unit testing I would like to cover both methods. so basically, the "deleteBook" is the easy one, where you simply expect DELETE on you $httpBackend, but the tricky one is the confirmation.

If we will break it down, the process is: display a dialog with a certain caption to it, wait for that dialog "promise" and act according to the decision (in our case "yes" invokes the deletBook method).
how do you do that? that's right, you mock it. but how?

Obviously you have some sort of a DialogService which makes your work mach easier, and on it you have a nice "confirm(msg)" method which does what take, but how exactly are you going to mock it? it's not just making sure that DialogService.confirm(msg) was called but it has to return a promise which you can later work with. It's actually pretty straight forward

First thing, let's create a MockDialogService. We do that for the sake of spying after it's confirm method:
MockDialogService = {
confirm: function(msg) {
}
};

So now you can spy on this method, but here is the nice trick - by spying on it you can define what returns when you call it, and in our case we would like to return a resolved promise with the value of "yes" (cause this is what we're testing now):
var deferred = $q.defer();
deferred.resolve('yes');
spyOn(MockDialogService, 'confirm').andReturn({result:deferred.promise});

Now that we have that we can make our expectation from it, like so:
expect(MockDialogService.confirm).toHaveBeenCalledWith('Are you out of your mind?!');

Nice. Now we need to see how this invokes the deleteBook method. so we spy on it:
spyOn(yourCtrl, 'deleteBook');

And here comes the tricky part. Since promises will be executed on the next Angular Digest cycle, we need to trigger it. We do this with scope.$apply(). After that we can expect that the deleteBook() method will be called:
scope.$apply();
expect(ctrl.deleteBook).toHaveBeenCalled();


Cheers

No comments: