Introduction

In the last tutorial of Unit test in Angular, we introduced the common test case pattern that you should follow and built in matchers that you can use when writing test cases. However, as applications get more complex, a function may chain up with multiple functions, which makes it harder to unit test a single function. The idea of unit tests is to split applications into multiple units and then test them one at a time. This is where Spies from Jasmine come into play.

What are Spies?

Spy is a feature in Jasmine that allows you to spy on something to achieve the following goals:

  1. Monitor if a function is called along with the parameters pass to it
  2. Override function return values or properties to simulate desired situations during tests
  3. Override the implementation of functions completely

Spy can be used against properties and functions including natvie functions in the window object, functions that you define in your component, or even API calls.

P.S. Even though Spies can be used on API calls, you can only override return values, but not the HTTP status code. We will cover how to handle asynchronous API calls in another chapter.

Basically speaking, Spies will be your go to solution whenever you wish to override functions in your tests.

How to use Spies

For this section, we will use the following code to demonstrate how Spies can be used in unit tests.

Person Interface
Person Interface
serveDrink and canDrinkAlcohol function
serveDrink and canDrinkAlcohol function

The basic syntax of using a Spy is:

spyOn(Object to spy, function or property to spy)

Spy takes in two arguments:

  1. The object you want to spy upon
  2. The function name or property name of the object you want to spy

Test if function is called

In the sample code above, we expect the canDrinkAlcohol function will be called when serveDrink is invoked.

We can test this behaviour with the following test case:

Test if function is called
Test if function is called

On line 47, we set up the spy and save it to a variable called fnc.
On line 53, we call the serveDrink function.
On line 56, we test if the function we spy on has been called.

Note that the spy must be set before you call the function. If we put line 53 before line 47, the test will fail because we actually spy on the function after it’s already called.

Test function called result
Test function called result

Test arguments passed to function

The canDrinkAlcohol expects it’s caller to pass in the age information. We can verify arguments are indeed passed to it with the following test case.

Test function is called with correct argument
Test function is called with correct argument

The test case is pretty much the same as the previous one. We only change matcher function to the toHaveBeenCalledWith matcher.

Test function argument result
Test function argument result

Test number of time a function is called

If you wish to test how many times a function is called, you can follow the following test case.

Test number of times a function is called
Test number of times a function is called
Test number of times a function is called result
Test number of times a function is called result

The toHaveBeenCalledTimes matcher is especially useful when you have a loop and the number of times a function is called is related to the size of the array.

Override return value of the spied object

In some cases, you would like the child function to return specific values, so that the TestSuite can test the desired if statement path you want to test.

For instance

Override function return value test case
Override function return value test case

On line 89 we prepare the dummy data that we’ll be using during this test case. Note that our canDrinkAlcohol function will only return true if the input argument, age, is greater than or equal to 18. However, on line 92, we override the return value of canDrinkAlcohol by chaining our spy object with the returnValue function. Meaning that despite the input argument and despite how this function is implemented, the canDrinkAlcohol function will always return true whenever it is called.

Then on line 98, we check if the serveDrink will return Beer when canDrinkAlcohol functions return true.

Note. There is another function called returnValues that allows you to pass in more than one argument. For instance:

spyOn(component, ‘canDrinkAlcohol’).and.returnValues(true, false);

The statement above means that on the first call of the canDrinkAlcohol function, return true and on the second call of the canDrinkAlcohol function, return false.

Override function in test case

Suppose instead of the return value, you wish to override the implementation of the child function completely, you may use the following approach.

Override function in test case
Override function in test case

On line 106, we chain our spy with the callFake function. When callFake is specified, TestSuite will execute the fake function instead of the actual function defined in the ts file.

Override function result
Override function result

Conclusion

In this tutorial we have covered how Spies are used in unit tests. Mastering Spies will allow you to write a decent amount of tests with good coverage. In this next tutorial of Unit test in Angular, we will use what we have learned in this chapter to demonstrate how Angular components can be tested.