Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing Angular Service that uses $timeout with Jasmine's Mock Clock

I have a function inside one of my angular services that I'd like to be called repeatedly at a regular interval. I'd like to do this using $timeout. It looks something like this:

var interval = 1000; // Or something  var _tick = function () {      $timeout(function () {         doStuff();         _tick();     }, interval); };  _tick(); 

I'm stumped on how to unit test this with Jasmine at the moment - How do I do this? If I use $timeout.flush() then the function calls occur indefinitely. If I use Jasmine's mock clock, $timeout seems to be unaffected. Basically if I can get this working, I should be good to go:

describe("ANGULAR Manually ticking the Jasmine Mock Clock", function() {     var timerCallback, $timeout;      beforeEach(inject(function($injector) {         $timeout = $injector.get('$timeout');         timerCallback = jasmine.createSpy('timerCallback');         jasmine.Clock.useMock();     }));      it("causes a timeout to be called synchronously", function() {         $timeout(function() {             timerCallback();         }, 100);         expect(timerCallback).not.toHaveBeenCalled();         jasmine.Clock.tick(101);         expect(timerCallback).toHaveBeenCalled();     }); }); 

These two variations work, but do not help me:

describe("Manually ticking the Jasmine Mock Clock", function() {     var timerCallback;      beforeEach(function() {         timerCallback = jasmine.createSpy('timerCallback');         jasmine.Clock.useMock();     });      it("causes a timeout to be called synchronously", function() {         setTimeout(function() {             timerCallback();         }, 100);         expect(timerCallback).not.toHaveBeenCalled();         jasmine.Clock.tick(101);         expect(timerCallback).toHaveBeenCalled();     }); });  describe("ANGULAR Manually flushing $timeout", function() {     var timerCallback, $timeout;      beforeEach(inject(function($injector) {         $timeout = $injector.get('$timeout');         timerCallback = jasmine.createSpy('timerCallback');     }));      it("causes a timeout to be called synchronously", function() {         $timeout(function() {             timerCallback();         }, 100);         expect(timerCallback).not.toHaveBeenCalled();         $timeout.flush();         expect(timerCallback).toHaveBeenCalled();     }); }); 

Thanks in advance!

like image 422
Jesus is Lord Avatar asked Jul 02 '13 06:07

Jesus is Lord


People also ask

What is Jasmine testing framework and how do you use it for Angular unit testing?

Jasmine is a behavior development testing framework. Unit tests are written using Jasmine and are run to see if individual parts of an application are working correctly. As a result, unit tests will either pass or fail depending on if the code is working correctly or has a bug.

What unit testing does Angular use?

Jasmine and Karma frameworks are used for Unit Testing of the Angular applications.

How do I write a test case for setTimeout in Jasmine?

Jasmine supports testing async code. We can test async code with: describe("Using callbacks", function () { beforeEach(function (done) { setTimeout(function () { value = 0; done(); }, 1); }); it("supports sequential execution of async code", function (done) { value++; expect(value). toBeGreaterThan(0); done(); }); });

Which method is used to mock the service method response in Angular?

Prefer spies as they are usually the best way to mock services. These standard testing techniques are great for unit testing services in isolation. However, you almost always inject services into application classes using Angular dependency injection and you should have tests that reflect that usage pattern.


2 Answers

Do not make your test Async by using Jasmine's clock. Instead, use $timeout.flush() to synchronously maintain the flow of the test. It may be a bit tricky to setup, but once you get it then your tests will be faster and more controlled.

Here's an example of a test that does it using this approach: https://github.com/angular/angular.js/blob/master/test/ngAnimate/animateSpec.js#L618

like image 59
matsko Avatar answered Oct 11 '22 15:10

matsko


@matsko's answer led me down the right path. I thought I'd post my "complete" solution to make it simpler to find the answer.

The thing to test

angular.module("app").service("MyService", function() {     return {         methodThatHasTimeoutAndReturnsAPromise: function($q, $timeout) {             var deferred = $q.defer();             $timeout(function() {                 deferred.resolve(5);             }, 2000);             return deferred.promise;         }     }; }); 

The test

describe("MyService", function() {     var target,         $timeout;     beforeEach(inject(function(_$timeout_, MyService) {         $timeout = _$timeout_;         target = MyService;     }));     beforeEach(function(done) {         done();     });     it("equals 5", function(done) {         target.methodThatHasTimeoutAndReturnsAPromise().then(function(value) {             expect(value).toBe(5);             done();         });         $timeout.flush();     }); }); 
like image 27
Beez Avatar answered Oct 11 '22 16:10

Beez