Alefe Jamarino
Alefe Jamarino

Reputation: 11

Angular 18 + Jest - Unable to spy on signal set method

I have a component that changes the value of a signal declared in a service on its initialization. I'm trying to test this declaration using Jest and spying on the set method of the signal, but with no success. Running Angular 18.2.8 and Jest 29.7.0.

Goal: To verify that the component sets the loading signal to true in its ngOnInit method.

service.ts

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

@Injectable()
export class CardStateService {
loading = signal\<boolean\>(false);
}

component.ts

import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UntilDestroy } from '@ngneat/until-destroy';
import { CardStateService } from './card-state.service';

@UntilDestroy()
@Component({
selector: 'component-card',
templateUrl: './component.html',
styleUrls: \['./component.scss'\],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: \[ CommonModule\],
providers: \[CardStateService\],
})

export class Component implements OnInit {

stateService = inject(CardStateService);


ngOnInit(): void {
this.stateService.loading.set(true);
}

}

component.spec.ts

import { signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MockProvider } from 'ng-mocks';
import { CardStateService } from '../../shared/card-state.service';
import { Component } from './component';

describe('Component', () => {
  let component: Component;
  let fixture: ComponentFixture<Component>;
  let cardStateServiceMock: CardStateService;

  let mockStateService = {
    loading: signal(false),
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [Component],
      providers: [
        MockProvider(CardStateService, mockStateService),
      ],
    });

    fixture = TestBed.createComponent(Component);
    component = fixture.componentInstance;
    cardStateServiceMock = TestBed.inject(CardStateService);
    fixture.detectChanges();
  });

  it('should set loading to true when initializing component', () => {
    const loadingSpy = jest.spyOn(mockStateService.loading, 'set')
    component.ngOnInit();

    expect(loadingSpy).toHaveBeenCalledWith(true);
  });
});

Test result

 Component› should set loading to true when initializing component                                                                                                                                                                                                    

 expect(jest.fn()).toHaveBeenCalledWith(...expected)

 Expected: true

 Number of calls: 0

Upvotes: 1

Views: 192

Answers (1)

Naren Murali
Naren Murali

Reputation: 57986

We can achieve the passing testcase by using whenStable which returns a promise.

We can, place our testcase inside the then block and validate it.

it('should set loading to true when initializing App', () => {
  const loadingSpy = jest.spyOn(mockStateService.loading, 'set');
  component.ngOnInit();
  fixture.whenStable().then(() => {
    expect(loadingSpy).toHaveBeenCalledWith(true);
  });
});

As of now, the test case for testing will pass only when you place the expect block inside an asynchronous code block. So if you wrap the code inside a setTimeout the code will pass, so asynchronous delay of the test case will make it pass, while synchronous execution is causing it to fail.

it('should set loading to true when initializing App', () => {
  const loadingSpy = jest.spyOn(mockStateService.loading, 'set');
  component.ngOnInit();
  setTimeout(() => { // <- this also passes
    expect(loadingSpy).toHaveBeenCalledWith(true);
  });
});

But the techniques of flushEffects/ async await and fakeAsync flush and detectChanges are not working.

Stackblitz Demo

Upvotes: 0

Related Questions