Testing is an important part of JavaScript.
In this article, we’ll look at how to create more complex tests with Jasmine.
Testing Promises
We can test promises with Jasmine.
The beforeAll
, afterAll
, beforeEach
and afterEach
functions all support promises.
For instance, we can write:
describe("Using promises", function () {
let value;
beforeEach(function () {
return Promise.resolve().then(function () {
value = 0;
});
});
it("should support async execution of tests", function () {
return Promise.resolve().then(function () {
value++;
expect(value).toBeGreaterThan(0);
});
});
});
We called then
on a promise in the beforeEach
and it
callbacks.
Then we can check the values in there.
It also works with the async
and await
syntax.
For instance, we can write:
describe("Using promises", function () {
let value;
beforeEach(async function () {
await Promise.resolve()
value = 0;
});
it("should support async execution of tests", async function () {
await Promise.resolve()
value++;
expect(value).toBeGreaterThan(0);
});
});
We just rewrote the promises with the async
and await
syntax.
In both examples, they’ll run sequentially.
The test starts with the beforeEach
callback and then run the it
callback.
Changing Wait Time
The beforeEach
and afterEach
functions take an optional 2nd argument to set the wait until done
is finished.
For example, we can write:
describe("long asynchronous specs", function () {
beforeEach(function (done) {
done();
}, 1000);
it("takes a long time", function (done) {
setTimeout(function () {
done();
}, 9000);
}, 10000);
afterEach(function (done) {
done();
}, 1000);
});
to pass in a 2nd argument with the wait time in milliseconds.
Handling Failures
Handling failures with promises can be done like any other piece of code.
If a promise is rejected in our test, then the test will fail.
For instance, if we have:
describe("promise test", function () {
it('does a thing', function () {
return Promise.reject()
});
});
then the test will fail.
With async
and await
, we get the same result:
describe("long asynchronous specs", function () {
it('does a thing', async function () {
await Promise.reject()
});
});
This will also fail.
We can fail a test with callbacks.
Since Jasmine 3.0, the done
function can detect if an Error
object is passed to the function.
If it is, then the test will fail.
For instance, we can write:
describe("long asynchronous specs", function () {
beforeEach(function (done) {
setTimeout(function () {
let err;
try {
throw new Error()
} catch (e) {
err = e;
}
done(err);
});
});
it('does something', () => { })
});
Our beforeEach
callback throw an error.
So since we call done
with the err
object, we’ll see that the test will fail.
Mock Clock
We can use the mock clock to avoid writing async tests.
It’ll make async code run synchronously.
For instance, we can write:
describe("long asynchronous specs", function () {
beforeEach(function () {
jasmine.clock().install();
});
afterEach(function () {
jasmine.clock().uninstall();
});
it('does something after 10 seconds', function () {
const callback = jasmine.createSpy('callback');
setTimeout(function () {
callback('foo');
}, 10000);
jasmine.clock().tick(10000);
expect(callback).toHaveBeenCalledWith('foo');
});
});
to create a test with the mock clock.
We called jasmine.clock().install()
to create the clock.
And then we run the test code by calling tick
to move to the time we want.
Then when the test is done, we call jasmine.clock().uninstall()
to remove the mock clock.
Conclusion
We can test various kinds of async code with Jasmine.
Promises and callbacks are all supported.
And we can make timer code synchronous with a mock clock.