angularjasminekarma-jasmine

spyOn primeng's MessageService with Jasmine


I struggle a bit with testing adding new message with primeng's MessageService.

Here is my component's code:

@Component({
  selector: 'app-get-menu',
  templateUrl: './get-menu.component.html',
  styleUrls: ['./get-menu.component.scss'],
  providers: [MessageService],
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  imports: [Toast],
})
export class GetMenuComponent implements OnInit {

  // Var
  menu: MenuInterface[] = [];

  // Constructor
  constructor(
    private apiService: ApiService,
    private router: Router,
    private dateTransformerService: DateTransformerService,
    private messageService: MessageService
  ) {}


  ngOnInit() {
    this.initData();
  }


  initData() {
    this.apiService.getMenu().subscribe({
      next: (menu) => {
        this.menu = menu;
      },
      error: (error) => {
        this.messageService.add({
          severity: "error",
          summary: "Error",
          detail: "Couldn't load menu",
          life: 10000,
        });
      }
    });
  }

Here is my spec.ts code:

describe('GetMenuComponent', () => {

  let component: GetMenuComponent;
  let fixture: ComponentFixture<GetMenuComponent>;
  let apiService: ApiService;
  let messageService: MessageService;
  let router: Router;
  let dateTransformerService: DateTransformerService;


  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [GetMenuComponent],
      providers: [ApiService,MessageService, provideHttpClient(), 
                  provideHttpClientTesting(), Router, DateTransformerService]

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

    apiService = TestBed.inject(ApiService);
    router = TestBed.inject(Router);
    dateTransformerService = TestBed.inject(DateTransformerService);
    messageService = TestBed.inject(MessageService);
  });

  //already tried without the fakeAsync wrapper
  it("Should handle errors and show error message", fakeAsync(() => {
    
    spyOn(apiService, 'getMenu').and.returnValue(throwError(() => new Error("some error")));
    //already tried with spyOn(apiService, 'getMenu').and.throwError("some error");
    spyOn(messageService, 'add');
    component.initData();

    tick(200);
    expect(messageService.add).toHaveBeenCalled();
  }))

The test always fail with the following error:

Expected spy add to have been called.

If I replace the messageService.add({...}) call to another random function call in my component and try to test if this random function is being called, the test succeed. Am I missing something?


Solution

  • The first thing I notice is that configureTestingModule is not wrapped in a waitForAsync. The method is asynchronous and should be wrapped by waitForAsync before running the test cases.

    Basics of testing components - CLI-generated tests - Angular.dev

      beforeEach(waitForAsync(() => {
        TestBed.configureTestingModule({
          imports: [GetMenuComponent],
          providers: [ApiService,MessageService, provideHttpClient(), 
                      provideHttpClientTesting(), Router, DateTransformerService]
    
        });
        fixture = TestBed.createComponent(GetMenuComponent);
        fixture.detectChanges();
        component = fixture.componentInstance;
    
        apiService = TestBed.inject(ApiService);
        router = TestBed.inject(Router);
        dateTransformerService = TestBed.inject(DateTransformerService);
      }));
    

    Could be a timing issue. Just swap our tick with flush so that the API is completed and does not depend on timing (200).

    You are having MessageService in the providers array of the component, so it will be a separate instance from the root service. You have to access this service through the component as shown below.

      it("Should handle errors and show error message", fakeAsync(() => {
        
        spyOn(apiService, 'getMenu').and.returnValue(throwError(() => new Error("some error")));
        //already tried with spyOn(apiService, 'getMenu').and.throwError("some error");
        spyOn(component['messageService'], 'add');
        component.initData();
    
        flush();
        expect(component.messageService.add).toHaveBeenCalled();
      }))