https://fast.wistia.com/embed/medias/xfas7bz2dm.jsonphttps://fast.wistia.com/assets/external/E-v1.js
This is the 17th Testing Tuesday episode. Every week we will share our insights and opinions on the software testing space. Drop by every Tuesday to learn more! Last week we introduced testing JavaScript with Jasmine.
Spying on JavaScript methods using Jasmine
Jasmine is made for unit testing. Unit tests are supposed to test only one component of your application. A component can be a function, an object, a module, basically everything self-contained that acts like a black box to the outside world. You usually want to avoid that your unit tests fail because another component failed. That's why you want to test your components in isolation as much as possible.
For example, you may not want to send data to another server in your unit tests or you don't want to manipulate a page's DOM. But you want to make sure that the components which are responsible for these tasks get called correctly. How can we do that?
Jasmine provides a feature called spies. A spy listens to method calls on your objects and can be asked if and how a method got called later on.
In this screencast, we show you how you can use spies to check if methods got called. We check if data gets sent to the server without ever performing a request by spying on jQuery's ajax
method.
Up next Testing Tuesday:
Next week we'll show you how to test asynchronous JavaScript operations with Jasmine. If you've got any questions or suggestions, please leave us a comment!
Further info:
Transcript
Spying on Javascript methods with Jasmine
Intro
Ahoy and welcome! My name is Clemens Helm and you're watching Codeship Testing Tuesday #17! Last week we introduced Jasmine which is a library for Javascript unit testing. We wrote some specs and asserted expectations. Today we'll spy on methods of objects to see if they get called or not.
Screencast
Jasmine is made for unit testing. Unit tests are supposed to test only one component of your application. A component can be a function, an object, a module, basically everything self-contained that acts like a black box to the outside world. You usually want to avoid that your unit tests fail because another component failed. That's why you want to test your components in isolation as much as possible.
For example, you don't want to send data to another server in your unit tests or you don't want to manipulate a page's DOM. But you want to make sure that the components which are responsible for these tasks get called correctly. How can we do that?
Jasmine provides a feature called spies. A spy listens to method calls on your objects and can be asked if and how a method got called later on. Let's say we've got a web frontend application that should send very important information to our web server. We've already got a method for this:
function VeryImportantInformation () {}; VeryImportantInformation.send = function (information) { };
We can't actually test that the right request gets sent to the server in the unit test, but we know that we want to accomblish it using jQuery's ajax method. So we can just put a spy on this method to see if it called with the right argument:
describe("very important information", function () { it("shoud be sent", function () { spyOn(jQuery, "ajax"); }); });
This spy will replace the ajax
method with a stub that tracks if the method got called. The actual ajax
method won't be called anymore, but that's what we want, because we want to test this behavior in isolation.
Let's add the method call now:
describe("very important information", function () { it("shoud be sent", function () { spyOn(jQuery, "ajax"); VeryImportantInformation.send({i_am: "the walrus"}); }); });
After the method call we want to check if the ajax
method actually got called with the right parameters:
describe("very important information", function () { it("shoud be sent", function () { spyOn(jQuery, "ajax"); VeryImportantInformation.send({"i am": "the walrus"}); expect(jQuery.ajax).toHaveBeenCalledWith({ method: "POST", url: "/important_information", data: {"i am": "the walrus"} }); }); });
So let's open the SpecRunner.html
file now to see what's the current status of our method. Jasmine fails saying that the ajax
method was never called with the given values. So our spy did its job well and found out that we never called the ajax
method.
Let's implement the send
method the easiest way possible to get our spec working:
function VeryImportantInformation () {}; VeryImportantInformation.send = function (information) { jQuery.ajax({ method: "POST", url: "/important_information", data: {"i am": "the walrus"} }); };
And this makes the spec work. But hold on, we just hardcoded the test data and didn't pass the actual information! We could write another spec with different data to make it impossible to implement the method this way. But I usually like to use random data instead, because it is less effort to write. Let's rewrite our spec to use random values:
describe("very important information", function () { it("shoud be sent", function () { spyOn(jQuery, "ajax"); var information = {"i am": Math.random()}; VeryImportantInformation.send(information); expect(jQuery.ajax).toHaveBeenCalledWith({ method: "POST", url: "/important_information", data: information }); }); });
Now our spec fails of course. To make it work again, we could try to pass Math.random()
instead of "the walrus"
.
VeryImportantInformation.send = function (information) { jQuery.ajax({ method: "POST", url: "/important_information", data: {"i am": Math.random()} }); };
But this fails as well, because Math.random()
generates a different value everytime it is called. So the only way we can make the spec work now is by passing our information to the ajax call:
VeryImportantInformation.send = function (information) { jQuery.ajax({ method: "POST", url: "/important_information", data: information }); };
Now our spec works!
You might be wondering why I didn't implement it this way in the first place. When I develop software, I write my tests first and then I try to make these tests work with as simple code as possible. I try to implement it wrong or incomplete deliberately, because this way I can prove that my tests aren't sufficient yet. Working this way makes your test suite rock-solid and it's very unlikely that flaws sneak into your application.
To sum up, when should you use spies and when not?
I think spies are handy everytime you deal with 3rd party libraries or with code you can't test entirely because it breaks the scope of your tests. But one problem of spies is, that they are tightly coupled to your implementation. For example, we could use jQuery's post
method instead of the ajax
method:
VeryImportantInformation.send = function (information) { jQuery.post("/important_information", information); };
Even though the post
method calls the ajax
method internally, our spec fails now, because it expected additional arguments:
Expected spy ajax to have been called with [ { method : 'POST', url : '/important_information', data : { i am : 0.9397278234828264 } } ] but actual calls were [ { url : '/important_information', type : 'post', dataType : undefined, data : { i am : 0.9397278234828264 }, success : undefined } ]
So spies make it harder to change your code later on. That's why it can become cumbersome to work with them if your application changes frequently.
Outro
This was it for today, but there will be more on Jasmin next Testing Tuesday. We'll explore how to test asynchronous Javascript operations. Don't miss out on that and of course – always stay shipping!