With the server running, open your browser at http://0.0.0.0:8000/SpecRunner.html
, to see the results of ours specs.
You can see that even though the server is running, and the spec appears to be correct, it is failing. It's 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(23.67); });
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.
To tell Jasmine to wait for an asynchronous call, we need to use another of its global functions, waitsFor()
.
Before we can dig into how it works, let's jump ahead and adapt the previous test code to use this new function:
describe("when fetched", function() { var fetched = false; beforeEach(function() { stock.fetch({ success: function () { fetched = true; } }); waitsFor(function (argument) { return fetched; }, 'Timeout fetching stock data', 2000); }); it("should update its share price", function() { expect(stock.sharePrice).toEqual(23.67); }); });
The first thing you will notice is that we have added a success
callback to the stock.fetch()
function, to set the fetched
variable to true
after the fetch is complete:
stock.fetch({ success: function () { fetched = true; } });
Its implementation is as follows:
Stock.prototype.fetch = function(parameters) { var that = this; var params = parameters || {}; var success = params.success || function () {}; var url = 'http://0.0.0.0:8000/stocks/'+that.symbol; $.getJSON(url, function (data) { that.sharePrice = data.sharePrice; success(that); }); };
Then we use the waitsFor()
function to hold the execution of the it
block, until the fetched
variable is true
:
waitsFor(function (argument) {
return fetched;
}, 'Timeout fetching stock data', 2000);
And if the stock isn't fetched in 2000 milliseconds, it throws an error, making the spec fail.
Let's recap, the waitsFor()
function accepts three parameters:
function (argument) { return fetched; }
"Timeout fetching stock data"
2000
.So whenever you have any expectations that depend on the result of an asynchronous call, you can hold its execution by using the waitsFor()
function inside a beforeEach
block.
Next, we will see how to use the waitsFor()
function directly inside the it
block.
We have seen that we can use the waitsFor()
function inside beforeEach
, but what if we need to write a test code that has an asynchronous call inside an it
block?
As an exercise, let's rewrite the previous spec without nesting it in a describe
block, but rather as single it
block:
it("should be able to update its share price", function() { var fetched = false; stock.fetch({ success: function() { fetched = true; } }); waitsFor(function (argument) { return fetched; }, 'Timeout fetching stock data', 2000); expect(stock.sharePrice).toEqual(23.67); });
By running this example, you will see that the problem of synchronism has come back. That is because the waitsFor()
function is not blocking the execution.
It worked previously, because Jasmine waits to run the it
block until waitsFor()
has been completed.
So we need a way to schedule this expectation code to be run after the waitsFor()
completes. As you might have guessed, it is going to be through another Jasmine global function, the runs
function.
All you have to do is move the code that you want to respect the asynchronous behavior inside a runs
block:
it("should be able to update its share price", function() { var fetched = false; stock.fetch({ success: function() { fetched = true; } }); waitsFor(function (argument) { return fetched; }, 'Timeout fetching stock data', 2000); runs(function() { expect(stock.sharePrice).toEqual(23.67); }); });
That way, Jasmine runs that code only after waitsFor()
completes.
And you can even put multiple runs
blocks, and they will run in the order they were declared:
it("should be able to update its share price", function() { var fetched = false; runs(function() { stock.fetch({ success: function() { fetched = true; } }); });waitsFor(function (argument) {
return fetched;}, 'Timeout fetching stock data', 2000);
runs(function() { expect(stock.sharePrice).toEqual(23.67); }); runs(function() { expect(stock.sharePrice).not.toBeUndefined(); }); runs(function() { expect(stock.sharePrice).toBeGreaterThan(0); }); });