SoFunction
Updated on 2025-04-04

Detailed tutorial on using spy in Angular test

Introduction

Jasmine spy is used to track or stub functions or methods. spy is a method that checks whether a function is called or provides a custom return value. We can use spy to test components that depend on services and avoid actually calling the service's methods to get the value. This helps keep our unit tests focused on testing the internals of the component itself rather than its dependencies.

In this article, you will learn how to use Jasmine spy in your Angular project.

Prerequisites

To complete this tutorial, you need:

  • Install locally
  • Some basics about setting up Angular projects.

This tutorial has used Node v16.2.0,npmv7.15.1 and@angular/corev12.0.4 for verification.

Step 1 — Set up the project

Let's use an example that is very similar to the example we used in the Angular unit test introduction.

First, use@angular/cliCreate a new project:

ng new angular-test-spies-example

Then, switch to the newly created project directory:

cd angular-test-spies-example

Previously, the app used two buttons to increase and decrease values ​​between 0 and 15.

For this tutorial, the logic will be moved to a service. This will allow multiple components to access the same central value.

ng generate service increment-decrement

Then, open theAnd replace the content with the following code:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class IncrementDecrementService {
  value = 0;
  message!: string;

  increment() {
    if ( < 15) {
       += 1;
       = '';
    } else {
       = 'Maximum reached!';
    }
  }

  decrement() {
    if ( > 0) {
       -= 1;
       = '';
    } else {
       = 'Minimum reached!';
    }
  }
}

Open the    in your code editorAnd replace the content with the following code:

import { Component } from '@angular/core';
import { IncrementDecrementService } from './';

@Component({
  selector: 'app-root',
  templateUrl: './',
  styleUrls: ['./']
})
export class AppComponent {
  constructor(public incrementDecrement: IncrementDecrementService) { }

  increment() {
    ();
  }

  decrement() {
    ();
  }
}

Open the    in your code editorAnd replace the content with the following code:

<h1>{{  }}</h1>

<hr>

<button (click)="increment()" class="increment">Increment</button>

<button (click)="decrement()" class="decrement">Decrement</button>

<p class="message">
  {{  }}
</p>

Next, open theAnd modify the following line of code:

import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './';
import { IncrementDecrementService } from './';

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let debugElement: DebugElement;
  let incrementDecrementService: IncrementDecrementService;

  beforeEach(waitForAsync(() => {
    ({
      declarations: [
        AppComponent
      ],
      providers: [ IncrementDecrementService ]
    }).compileComponents();

    fixture = (AppComponent);
    debugElement = ;

    incrementDecrementService = (IncrementDecrementService);
  }));

  it('should increment in template', () => {
    debugElement
      .query((''))
      .triggerEventHandler('click', null);

    ();

    const value = (('h1')).;

    expect(value).toEqual('1');
  });

  it('should stop at 15 and show maximum message', () => {
     = 15;
    debugElement
      .query((''))
      .triggerEventHandler('click', null);

    ();

    const value = (('h1')).;
    const message = (('')).;

    expect(value).toEqual('15');
    expect(message).toContain('Maximum');
  });
});

Please note that we can useGet a reference to the injection service.

Testing our components in this way is valid, but the actual calls will also be passed to the service, and our components are not tested in orphaned. Next, we will explore how to use spy to check if a method has been called or provides a stub return value.

Step 2 —— Methods of monitoring services

Here's how to use JasminespyOnThe function calls a service method and tests whether it is called:

import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './';
import { IncrementDecrementService } from './';

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let debugElement: DebugElement;
  let incrementDecrementService: IncrementDecrementService;
  let incrementSpy: any;

  beforeEach(waitForAsync(() => {
    ({
      declarations: [
        AppComponent
      ],
      providers: [ IncrementDecrementService ]
    }).compileComponents();

    fixture = (AppComponent);
    debugElement = ;

    incrementDecrementService = (IncrementDecrementService);
    incrementSpy = spyOn(incrementDecrementService, 'increment').();
  }));

  it('should call increment on the service', () => {
    debugElement
      .query((''))
      .triggerEventHandler('click', null);

    expect().toBe(1);
    expect(incrementSpy).toHaveBeenCalled();
  });
});

spyOn accepts two parameters: a class instance (in this case our service instance) and a string value that indicates the name of the method or function to be monitored.

Here, we also link .() on spy so that the actual method will still be called. In this case, our spy is only used to determine whether the method is called and to monitor parameters.

Here is an example of an assertion method being called twice:

expect(incrementSpy).toHaveBeenCalledTimes(2);

The following is the assertion method is not used'error'Example of parameter call:

expect(incrementSpy).('error');

If we want to avoid actually calling methods on the service, we can use them on spy.

Our example methods are not suitable for doing this, because they do not return anything, but instead change internal properties.

Let's add a new way to the service to the actual return value:

minimumOrMaximumReached() {
  return !!( && );
}

We also add a new method to the component, which the template will use to get the value:

limitReached() {
  return ();
}

We can then test whether our template message will be displayed when the limit is reached without actually calling the method on the service:

import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './';
import { IncrementDecrementService } from './';

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let debugElement: DebugElement;
  let incrementDecrementService: IncrementDecrementService;
  let minimumOrMaximumSpy: any;

  beforeEach(waitForAsync(() => {
    ({
      declarations: [
        AppComponent
      ],
      providers: [ IncrementDecrementService ]
    }).compileComponents();

    fixture = (AppComponent);
    debugElement = ;

    incrementDecrementService = (IncrementDecrementService);
    minimumOrMaximumSpy = spyOn(incrementDecrementService, 'minimumOrMaximumReached').(true);
  }));

  it(`should show 'Limit reached' message`, () => {
    ();

    const message = (('')).;

    expect(message).toEqual('Limit reached!');
  });
});

in conclusion

In this article, you learned how to use Jasmine spy in your Angular project.

This is the introduction to this article about the detailed explanation of the tutorial on using spy in Angular testing. For more related content on using spy in Angular, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!