CreateSpyObject works in "beforeEach" but not in "it"

I have a component :

@Component({
  selector: 'app-sidenav',
  standalone: true,
  imports: [
    MatSidenavModule
  ],
  templateUrl: './sidenav.component.html',
  styleUrl: './sidenav.component.scss'
})
export class SidenavComponent implements OnInit {

  openingService = inject(SidenavOpeningService);
  opened: BooleanInput;

  #injector = inject(Injector);
  constructor() {
  }

  ngOnInit():void {
    runInInjectionContext(this.#injector, () => {
      effect(() => {
        this.opened = this.openingService.isOpen();
      });
    });
  }

}

<mat-sidenav-container>
  <mat-sidenav #sidenav [opened]="opened" (openedChange)="openingService.isOpen.set($event)"  mode="over">Start</mat-sidenav>
  <mat-sidenav-content>
    <p>test</p>
  </mat-sidenav-content>
</mat-sidenav-container>

and a service :

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

@Injectable({
  providedIn: 'root'
})
export class SidenavOpeningService {
  isOpen = signal(false);


  constructor() { }

  toggle() {
    this.isOpen.update(value => !value);
  }

}

This test pass :

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SidenavComponent } from './sidenav.component';
import {SidenavOpeningService} from "../../services/sidenav-opening.service";
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import {MatSidenavModule} from "@angular/material/sidenav";

describe('SidenavComponent', () => {
  let component: SidenavComponent;
  let fixture: ComponentFixture<SidenavComponent>;
  let mockService: jasmine.SpyObj<SidenavOpeningService> = jasmine.createSpyObj('SidenavOpeningService', ['isOpen', 'toggle']);


  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [SidenavComponent,MatSidenavModule, NoopAnimationsModule],
      providers: [{ provide: SidenavOpeningService, useValue: mockService }]
    })
    .compileComponents();

    fixture = TestBed.createComponent(SidenavComponent);
    component = fixture.componentInstance;
    mockService.isOpen.and.returnValue(true);
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should update "opened" based on SidenavOpeningService', () => {
    expect(component.opened).toBeTrue();
  });
});

but this test doesn't pass :

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SidenavComponent } from './sidenav.component';
import {SidenavOpeningService} from "../../services/sidenav-opening.service";
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import {MatSidenavModule} from "@angular/material/sidenav";

describe('SidenavComponent', () => {
  let component: SidenavComponent;
  let fixture: ComponentFixture<SidenavComponent>;
  let mockService: jasmine.SpyObj<SidenavOpeningService> = jasmine.createSpyObj('SidenavOpeningService', ['isOpen', 'toggle']);


  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [SidenavComponent,MatSidenavModule, NoopAnimationsModule],
      providers: [{ provide: SidenavOpeningService, useValue: mockService }]
    })
    .compileComponents();

    fixture = TestBed.createComponent(SidenavComponent);
    component = fixture.componentInstance;
    
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should update "opened" based on SidenavOpeningService', () => {
    mockService.isOpen.and.returnValue(true);
    fixture.detectChanges();
    expect(component.opened).toBeTrue();
  });
});

I have : Expected undefined to be true. at at UserContext.apply (src/app/core/components/sidenav/sidenav.component.spec.ts:32:30) at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:26) at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:273:39) at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:367:52)

Upvotes: 1

Views: 40

Answers (2)

SaschaA1982
SaschaA1982

Reputation: 81

Calling fixture.detectChanges() in the beforeEach is the problem.

Remove it and call it from the it function after configuring the spy object.

Upvotes: 0

Naren Murali
Naren Murali

Reputation: 57986

We need to initialize the component, after you setup the jasmine spys this fixes the failure!

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MatSidenavModule } from '@angular/material/sidenav';
import { SidenavComponent } from './sidenav-responsive-example';
import { SidenavOpeningService } from './sidenav-opening.service';

describe('SidenavComponent', () => {
  let component: SidenavComponent;
  let fixture: ComponentFixture<SidenavComponent>;
  let mockService: jasmine.SpyObj<SidenavOpeningService> = jasmine.createSpyObj(
    'SidenavOpeningService',
    ['isOpen', 'toggle']
  );

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [SidenavComponent, MatSidenavModule, NoopAnimationsModule],
      providers: [{ provide: SidenavOpeningService, useValue: mockService }],
    }).compileComponents();

    fixture = TestBed.createComponent(SidenavComponent);
    component = fixture.componentInstance;

    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should update "opened" based on SidenavOpeningService', () => {
    mockService.isOpen.and.returnValue(true);

    fixture = TestBed.createComponent(SidenavComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    expect(component.opened).toBeTrue();
  });
});

stackblitz demo -> ctrl+C to kill default command in terminal -> npm run test to start test cases!

Upvotes: 1

Related Questions