神刀安全网

#4 Testing with Jasmine – spies

Hello everyone! Welcome to the fourth article in my “testing with Jasmine” series! In theprevious post I presented matchers and several other functions. This time we will discuss “spies” which are a kind of stubs in the Jasmine test framework. We can use them to “spy” if functions are invoked and the parameters that were passed to them. Ok, let’s start our journey!

Basic usage of spies

I think it would be best to start just by showing an example and explaining everything on top of it. So please see below a sample test suite which uses spies:

describe("A spy", () => {   let car;   let wheel = null;     beforeEach(() => {     car = {       changeWheel: newWheel => {         wheel = newWheel;       }     };       spyOn(car, 'changeWheel');   });     it("tracks that function was called", () => {     car.changeWheel(1);          expect(car.changeWheel).toHaveBeenCalled();   });     it("tracks all the arguments of its calls", () => {     car.changeWheel(123);     car.changeWheel(1, 2);              expect(car.changeWheel).toHaveBeenCalledWith(123);     expect(car.changeWheel).toHaveBeenCalledWith(1, 2);   });     it("stops all execution on a function", () => {     car.changeWheel(1);          expect(wheel).toBeNull();   }); });

Ok, let’s see what is going on here… If you read my previous posts, you should be familiar with all the “syntax” of Jasmine tests. Basically, we are testing a car object here – it has a method called changeWheel and we want to check if it was called.

In line 12 we have a definition of the spy . The spyOn function takes an object as a first parameter and the name of the method (as a string) to spy on. From now on, all the method calls will be spied on by the spy.

Now, if we look at the first spec, we can see that we just call the changeWheel function. Then, we have an assertion in which we expect that the function was called – please note the special matcher we used here: toHaveBeenCalled . Easy right?

But toHaveBeenCalled is not the only available one. The second spec shows how can we use the spy to check what parameters were passed to the method. To do so, we have the toHaveBeenCalledWith matcher – it takes the expected value as a parameter.

The third spec shows how spies work. As you can see, we expect that the wheel parameter is still null even after the car.changeWheel method call. This is because the spy is a kinda stub – when we create a spy, we tell Jasmine that we only expect the function call but what the function does is outside of our interest.

The example above was really simple. We spied on the method which was called directly. But we can also spy on functions which are called by other functions. Let’s see a modification of the car object and tests for it:

describe("A spy", () => {   let car;   let wheel = null;     beforeEach(function() {     car = {       changeWheel: newWheel => {         wheel = this.getWheel(newWheel);       },       getWheel: item => item * 2     };       spyOn(car, 'getWheel');   });     it("tracks internally called function", () => {     car.changeWheel(1);          expect(car.getWheel).toHaveBeenCalled();   }); });

This time the changeWheel function internally calls the getWheel function. And this time the spy is set up to watch this getWheel method. In specs it works exactly the same – even if we call the changeWheel function, we can still check if the getWheel function was called.

Mocking behaviour of the spied function

I wrote before that spies are kinds of stubs. But we can change this behaviour and tell Jasmine that we want it to call its implementation instead. To do so, we have a suitable API available. Please see an example below:

spyOn(car, "getWheel").and.callThrough();

This will cause the getWheel function to still be spied on but it will also be invoked as usual.

Ok, but what if we need both behaviours at the same time? Let’s imagine that we’ve just used the above code in the test and, after that, we want to spy on the getWheel function as a stub without calling its implementation. We can achieve that in Jasmine! To do so, just call the code below:

car.getWheel.and.stub();

From now on, the next call of the changeWheel function will call the getWheel function internally as an ordinary stub.

But that is not all. We have several other possibilities with the “spies API” in Jasmine. For example, we can mock the value returned by the spied function. This is how to do that:

spyOn(car, "getWheel").and.returnValue(7);

This time, when we call the ’changeWheel function, Jasmine will call the getWheel` internally and will return 7.

But this is the simplest scenario. Besides returning a value, we can replace the whole implementation of the function. Please see below:

spyOn(car, "getWheel").and.callFake(() => {   return 3; });

This is very useful for mocking, e.g. ajax calls – in tests, we probably don’t want to make a real call to the service…

And the last example in this section! With spies, we can mock throwing an exception:

spyOn(car, "getWheel").and.throwError("!!!!");

I don’t think it needs any explanation 😉

The calls property

Every single call of the spied function is stored in the object exposed on the calls property. This object is very useful in assertions. For example:

expect(car.getWheel.calls.count()).toEqual(2);

As you can see, I used the count method available in the calls object. It tells us how many times this function was called. I’m sure you’ve guessed that there are more useful functions available. Of course you are right! Please see the list below:

  • any() – returns true if there was any call of the function and false if there wasn’t
  • count() – returns how many times the function was called
  • argsFor(index) – returns call parameter by provided index
  • allArgs() – returns parameters of all calls
  • all() – returns the context (the car object in our examples) and arguments of all calls
  • mostRecent() – the same as all() but only for the most recent call
  • first() – the same as all () but only for the first call
  • reset() – resets all tracking for a spy

One more thing here: for all() , mostRecent() and first() Jasmine adds a property object to the returned value. It is set to the value of this when the spy was called:

car.changeWheel(12);  expect(car.getWheel.first().object).toBe(car);

My advice to you is to check the examples of these tracking functions in the documentation of the Jasmine test framework. I think they will clarify any doubts.

Advanced use cases

Everything is fine when we just want to spy on a function which is an object’s method. Then the spyOn function is the most suitable for the situation. But what if we want to test a function which is not a part of any object? Well… the createSpy function takes the stage. Let’s see an example:

describe("A spy", () => {   let car;   let wheel = null;   let getWheel;     beforeEach(() => {     car = {       changeWheel: function(newWheel) {         wheel = getWheel(newWheel);       }     };       getWheel = jasmine.createSpy("getWheel");   });     it("tracks internally called function", () => {     car.changeWheel(1);          expect(getWheel).toHaveBeenCalled();   }); });

In the code above, the getWheel function is not a method of the car object. It’s not even a function. We just know that the changeWheel function calls that function internally so we create a spy for it (please remember that it’s just a stub) and test if it was really called.

These kinds of spies act like any other spies (tracking, calls, arguments, etc.). You can even mock them the same way as in spies created using the spyOn function. Please see below:

getWheel = jasmine.createSpy("getWheel"); getWheel.and.returnValue(1);

If we are talking about spies, we can do one more thing with Jasmine – we can create the whole object with multiple spies. To do so, we can use the createSpyObj function. Please see an example below:

describe("A spy", () => {   var car;     beforeEach(() => {     car = jasmine.createSpyObj('car', ['changeWheel']);   });     it("tracks calls of the function", () => {     car.changeWheel(1);          expect(car.changeWheel).toHaveBeenCalled();   }); });

This time we created an object called car with a spied on property changeWheel . This is similar to the previous example – we can track calls, arguments, etc.

Summary

That’s all for today! I think this is the most important part of my testing series (and of the Jasmine test framework). But it is not the end – in the next post I will show you how to test asynchronous calls! See ya next time! 😉

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » #4 Testing with Jasmine – spies

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮