With the server running, open your browser at http://localhost:8000/SpecRunner.html
to see the results of our specs.
You can see that even though the server is running, and the spec appears to be correct, it is failing. This is due to the fact that stock.fetch()
is asynchronous. A call to stock.fetch()
returns immediately, allowing Jasmine to run the expectations before the AJAX request is completed:
it("should update its share price", function() { expect(stock.sharePrice).toEqual(20.18); });
To fix this, we need to embrace the asynchronicity of the stock.fetch()
function and instruct Jasmine to wait for its execution before running the expectations.
In the example shown, we invoke the fetch
function during the spec's setup (the beforeEach
function).
The only thing we need to do to identify that this setup step is asynchronous is add a done
argument to its function definition:
describe("when fetched", function() {
beforeEach(function(done) {
});
it("should update its share price", function() {
expect(stock.sharePrice).toEqual(20.18);
});
});
Once Jasmine identifies this done
argument, it passes as its value a function that must be called once the asynchronous operation is completed.
So we could then pass this done
function as a success
callback of the fetch
function:
beforeEach(function(done) { stock.fetch({ success: done }); });
At the implementation, invoke it once the AJAX operation is completed:
Stock.prototype.fetch = function(params) { params = params || {}; var that = this; var success = params.success || function () {}; var url = 'http://localhost:8000/stocks/'+that.symbol; $.getJSON(url, function (data) { that.sharePrice = data.sharePrice; success(that); }); };
That is all there is to it; Jasmine will wait for the AJAX operation to be completed and the test will pass.
When required, it is also possible to have asynchronous afterEach
definitions using the same done
argument.
Another approach would be to have an asynchronous spec instead of an asynchronous setup. To demonstrate how this would work, we are going to need to rewrite our previous acceptance criteria:
describe("Stock", function() { var stock; var originalSharePrice = 0; beforeEach(function() { stock = new Stock({ symbol: 'AOUE', sharePrice: originalSharePrice }); }); it("should be able to update its share price", function(done) { stock.fetch(); expect(stock.sharePrice).toEqual(20.18); }); });
Again, all we have to do is add a done
argument to its function definition and invoke the done
function once the test is done:
it("should be able to update its share price", function(done) { stock.fetch({ success: function () { expect(stock.sharePrice).toEqual(20.18); done(); } }); });
The difference here is that we had to move the expectation for it to be inside the success
callback right before invoking the done
function.
When writing asynchronous specs, Jasmine will wait for 5 seconds, by default, for the done
callback to be called, failing the spec if it is not called before this timeout.
In this contrived example, where the server was a simple stub returning static data, that timeout was not a problem, but there are situations where that default time is not enough to complete an asynchronous task.
Although it is not recommended to have long-running specs, it is nice to know there is a way around this default behavior by changing a simple configuration variable in Jasmine called jasmine.DEFAULT_TIMEOUT_INTERVAL
.
To make it take effect in the entire suite, one could set it at the SpecHelper.js
file, as follows:
beforeEach(function() {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
jasmine.addMatchers({
// matchers code
});
});
jasmine.getFixtures().fixturesPath = 'spec/fixtures';
To make it take effect over a single spec, change its value in beforeEach
and restore it during afterEach
:
describe("Stock", function() { var defaultTimeout; beforeEach(function() { defaultTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; }); afterEach(function() { jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultTimeout; }); it("should be able to update its share price", function(done) { }); });