Unit Testing with Jasmine
A free video tutorial from Angular University
Best Selling Angular Courses | 200k+ students | 17 courses
20 courses
285,352 students
Learn more from the full course
Angular Testing In Depth (Signals Edition)
Learn Angular 22 testing in depth. Write robust, maintainable tests for modern signal-based Angular applications.
10:18:10 of on-demand video • Updated June 2026
Code in Github repository with downloadable ZIP files per section
Test Modern Angular Apps Built With Signals
Learn the Fundamentals of Vitest
Use Component Harnesses For Maintainable Tests
Test Angular Components, From Simple To Complex
Test Angular Services And HTTP Logic
Test Angular Forms, Directives, Pipes, And Routing
Debug Failing Tests Quickly
English [Auto]
Hello, everyone, and welcome back. In this new lesson, we are going to introduce the notion of Jasmine Spice. So let's say that we have here a method, the Add calculator method that is calling internally the logger service. Now let's suppose that this is an expensive service that consumes a lot of resources. So we would like to make sure that we only call the log function of the logger once whenever we execute the Add operation. So our test should fail. If we would call here, log a second time, for example. Let's see, how can we implement this test using the notion of a jasmine spy? So what we want to do here is we want to take the logger service that we have passed here to our calculator service, and we want to keep track of how many times some of its methods are called. In order to do that. Let's start by doing here a small refactoring. We are going to define here a logger constant that is going to contain our logger object. Now notice that here we are creating an actual instance of the dependency of the service that we are testing. This is just an example. Usually what we want to do is we want to provide mock or fake implementations of all the direct dependencies of the services that we are testing. This is so that we keep our test as a plain unit test. More on this later in this lesson. Right now, let's say that we still want to pass to the calculator service an instance of an actual logger, but we want to keep track of how many times some of its functions are called. So we can do that by using the spy on utility of Jasmine. We are going to pass to it the object that we want to spy as the first argument of spy on. So we want to spy on the logger object. The second argument that we need to pass in here is the list of methods that we want to spy on. So in this case, there is only one method, which is the log method. So what will happen here with the use of spy on is that Jasmine is going to take the original spy object and then Jasmine is going to replace some of its functions with a new function. So in this case, Jasmine is going to take the instance of our logger, and Jasmine is going to replace the log method with a new method, this new implementation of log, besides calling the original functionality that we see here, the new implementation of log will also keep track of how many times the function has been called. We will be able to use this information later, for example, for building test assertions. For example, we can now write here a new assertion saying that we expect that the logger log method will be called a certain number of times, so we can do that using the to have been called times assertion utility. And let's say that we would expect this method to be called only once. If we now run this test, we are going to see that all tests pass successfully. Let's say now that by accident we would modify here the implementation of the Add method in order to call here our log function a second time. Let's now try this second implementation of the Add function. So after a while we are going to see that our test has now failed and we have here the message we expected the spy log to have been called only once, but it was called two times. So as you can see, we are really asserting the number of times that the logger is getting cold. So let's fix the test and talk about how this works in practice. If we go back here to our specification, we can see that what we are doing here is we are creating an actual instance of a logger service and then we are spying on one of its methods. So we are adding extra functionality here to the log method that is going to help us to write our tests. Now alternatively to providing an actual implementation of the dependency and then spying on it. What we can do is to provide a complete fake version of the logger service without creating here an actual logger instance. This is actually the preferred approach for implementing unit tests. So whenever we are implementing a unit test, for example, for the calculator service, the only actual instance of a service that should be present in the test is the calculator service itself. Any other dependency of the calculator service should be mocked and replaced with a fake test implementation. In this case, the logger service is very simple. We are only logging here to the console, but in many cases our dependencies themselves have many other dependencies, such as, for example, external services that connect to a backend to a database. ET cetera. And we would not want to provide actual implementations of all. All those dependent services. What we want to do in this test is to focus on the functionality of the service being unit tested and nothing more. This is why a test like this is called a unit test. It's because we are testing here only a small unit of our overall system. In this case the calculator service. In this test, we want to test the functionality of the calculator service and nothing more. So that is why we are always providing an actual implementation of the calculator service. But then we are mocking or providing fake test implementations of all its dependencies. This will ensure that the calculator service unit test will fail only due to problems in the calculator service class and nothing more. So we would like, instead of creating here an actual logger service dependency, we would prefer to create here a fake dependency and we can do that using, for example, the jasmine spice functionality. For that we are going to use the Jasmine global object and we are going to call here the Create Spy object method. Let's give this spy object a name. We are going to call it simply logger service. And we are going to define here an array containing the list of methods of our fake logger implementation. We are going to add here only the log method. So now what we have here is a complete fake implementation of a logger service. This is an object created by Jasmine that contains only one method called log. We can now remove here the usage of spy on because this logger object is already being spied by Jasmine. So let's now run the tests and see what happens. So after a moment, we can see that now our tests are being executed successfully. So indeed, this logger service does contain a log method that is getting called here by the calculator without any problem. And we still have here the jasmine spying functionality that allowed us to write this assertion here. Now, notice one thing. If we clear here this log method from this array, then Jasmine is going to create here a fake object that has no functions that we can call. So here in our calculator service, whenever we call here the logger method on the fake jasmine implementation that we received, the Jasmine Spy, we would get here an error because this fake object would not contain the log method. So let's confirm that this is indeed the case. We are going to head over to the terminal and see that in the new execution of our test. This time around, our test is failing. So we have provided here an empty array and this is not possible. We need to specify here exactly what methods are getting spied by Jasmine. Let's then add back the log method, make sure that the tests are passing successfully and show here what can be further done using this jasmine spying functionality. So in the case of the log method, the method does not return any value. But if the method would return some sort of value, we could define it here using the jasmine spying functionality by using the and return value utility. So we could specify here a return value for this logger.log call. So as you can see, this functionality is very useful for having our mock test implementations to return some data if needed. We are going to see several examples of this later in this course. The concept of Jasmine spies is very important and we are going to be using it over and over again throughout the whole course. Let's then take a moment to summarize everything that we have learned here about Jasmine spies. As we can see, the Jasmine spying functionality allows us to do several very important things in our test. We can keep track of the number of times that a function is called. We can provide a fake implementation of a function and define what values it should return. We can either spy on an existing object or we can create a complete mock implementation of our dependency. Now that we have a solid understanding of the concept of Jasmine spies, let's continue the implementation of our test. We are going to learn how to structure our tests to avoid repeating the initialization logic of the tests. For example, here the creation of the Jasmine Spy, and we are going to introduce the notion of dependency injection in our tests, which is an essential concept in the angular framework.