Reputation: 343
I am attempting to setup a test of a setInterval call. setInterval always creates a new timer so no matter how far ahead you move tick() there will always be an error with this message:
Error: 1 timer(s) still in the queue.
If I override the setTimeout function with setInterval, then the test passes as expected. I have added code to allow this to my angular service using a public property myTimerFunc that is normally set to setInterval. The test code sets it to setTimeout.
Is there a better way to test setInterval?
It would be nice if the last timer in the queue would be ignored by jasmine. Then I could test without extra cruft.
For example this code will fail unless you uncomment the line that overrides setInterval. I do not mind doing this in the test code but I do want to avoid adding extra stuff to the normal code just to allow this override.
import { fakeAsync, tick } from '@angular/core/testing'
describe('testing setTimeout and setInterval:', () => {
it('setTimeout should work', fakeAsync(() => {
let callback = jasmine.createSpy('setTimeoutCallback')
setTimeout(() => {
callback()
}, 55)
tick(56)
expect(callback).toHaveBeenCalled();
}));
// let setInterval = setTimeout
it('setInterval should work', fakeAsync(() => {
let callback = jasmine.createSpy('setTimeoutCallback')
setInterval(() => {
callback()
}, 55)
tick(56)
expect(callback).toHaveBeenCalled();
}));
})
Upvotes: 2
Views: 4336
Reputation: 1005
Some theory about FakeAsync, flush: which will help you to understand the problem
By using fakeAsync we can ensure that all of our asynchronous code has run before we make assertions in our tests, and we even have fine-tuned control over how we want to advance time throughout the test.
Simplifying your question for understanding about the answer:
it('should test some async code', fakeAsync(() => {
let flag = false;
setTimeout(() => { flag = true; }, 100);
expect(flag).toBe(false); // PASSES
tick(50);
expect(flag).toBe(false); // PASSES
tick(50);
expect(flag).toBe(true); // PASSES
}));
Other possible alternative:
it('should test some asynccode', fakeAsync(() => {
let flag = false;
setTimeout(() => { flag = true; }, 100);
expect(flag).toBe(false);
flushMicrotasks();
expect(flag).toBe(true); // FAILS
}));
In your scenario,
flush() will work
Upvotes: 1
Reputation: 209002
If you wrapped the setInterval
in a service, you could mock the service and everything would be deterministic, with no side effects. Basically you'd just do something like
class IntervalService {
setInterval(callback: () => void, interval: number) {
return setInterval(callback, interval);
}
clearInterval(intervalId: number) {
clearInterval(intervalId);
}
}
The way you could mock it is by doing something like
class MockIntervalService {
callback;
setInterval(callback: () => any, interval: number) {
this.callback = callback;
return 1;
}
clearInterval(interval: number) {
callback = undefined;
}
tick() {
callback();
}
}
Then in your tests, you can control intervals by calling tick
on the mock service. The best part is that everything is synchronous, making the tests easier to reason about.
It's a little extra work to inject the service, but I don't think it's that much of a burden.
Upvotes: 0