2

I am working with an app based on Angular 12. I have service called notification service which handles toast messages from ngx-toastr library.

This is how that service looks like:

export class NotificationService {
  constructor(private toastr: ToastrService) {}

  showSuccess(message: string = 'Success ', note: string = ''): void {
    this.toastr.success(message, note);
  }

  showError(message: string = 'Error ', note: string = 'Try again'): void {
    this.toastr.error(message, note, {
      timeOut: 3000,
    });
  }
}

Service methods are working well, but when I am trying to implement tests for them then I get the following error:

NotificationService should test "showSuccess" method FAILED

Error: <spyOn> : could not find an object to spy upon for success()

These are the tests:

describe('NotificationService', () => {
  let notificationService: NotificationService,
    httpTestingController: HttpTestingController,
    toastrService: ToastrService,
    notificationServiceSpy: any;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [CommonModule, HttpClientTestingModule, ToastrModule.forRoot()],
      declarations: [],
      providers: [{ provide: ToastrService, useValue: toastrService }],
    }).compileComponents();
    notificationService = TestBed.inject(NotificationService);
    httpTestingController = TestBed.inject(HttpTestingController);
    toastrService = TestBed.inject(ToastrService);
  });

  it('should be created', () => {
    expect(notificationService).toBeTruthy();
  });

  it('should test "showSuccess" method', () => {
    spyOn(toastrService, 'success').and.callThrough();
  });

  afterEach(() => {
    httpTestingController.verify();
  });
});

Any help is appreciated. Thanks!

johannesMatevosyan
  • 1,974
  • 2
  • 30
  • 40

1 Answers1

4

You are getting that message because when you do:

providers: [{ provide: ToastrService, useValue: toastrService }],

At that point in time, toastrService is undefined and then when you do spyOn(toastrService, 'success'); there is no success method to be spied on because toastrService is undefined.

I would mock toastrService.

Make the following changes, pay attention to lines that start with !!.

describe('NotificationService', () => {
  let notificationService: NotificationService,
    httpTestingController: HttpTestingController,
    // !! change this line to this
    toastrService: jasmine.SpyObj<ToastrService>,
    notificationServiceSpy: any;

  beforeEach(async () => {
    // !! add a new spy object before each test, now toastrService is not undefined
    toastrService = jasmine.createSpyObj<ToastrService>('ToasterService', ['error', 'success']);
    await TestBed.configureTestingModule({
      imports: [CommonModule, HttpClientTestingModule, ToastrModule.forRoot()],
      declarations: [],
      providers: [
       // !! provide NotificationService to the TestBed module because it is under test
       NotificationService,
       { provide: ToastrService, useValue: toastrService }],
    }).compileComponents();
    notificationService = TestBed.inject(NotificationService);
    httpTestingController = TestBed.inject(HttpTestingController);
    // !! don't need the below line, we already have access to the spy object
    // toastrService = TestBed.inject(ToastrService);
  });

  it('should be created', () => {
    expect(notificationService).toBeTruthy();
  });

  it('should test "showSuccess" method', () => {
    // !! you should not spyOn this anymore, toastrService has methods error
    // and success now which are both spies.
    // spyOn(toastrService, 'success').and.callThrough();
    // !! call the method
    service.showSuccess('hello world', 'hello');
    expect(toastrService.success).toHaveBeenCalledWith('hello world', 'hello');
  });

  afterEach(() => {
    httpTestingController.verify();
  });
});
AliF50
  • 16,947
  • 1
  • 21
  • 37
  • 1
    Thank you so much for detailed answer. One question, since 'httpTestingController' is not needed, do we still have use 'afterEach'? – johannesMatevosyan Jan 07 '22 at 10:31
  • 1
    If your service does not inject `HttpClient`, you don't need `HttpClientTestingModule` and `HttpTestingController`. Check out this link for a good resource into learning Angular testing: https://testing-angular.com/. – AliF50 Jan 07 '22 at 14:10
  • Thanks, but is there any other reason of using 'afterEach' here? – johannesMatevosyan Jan 07 '22 at 14:32
  • 1
    The afterEach you have ensures that there are no http calls in queue. I don't think it is needed. – AliF50 Jan 07 '22 at 14:41