Testing behaviour inside observable subscription

56 Views Asked by At

I am not able to test the behaviour of a method that calls an API service and subscribes to it.

The code of my method looks something like this:

verify(): void {
  this.state = VerifyState.VERIFYING;
  this.apiService.verify(this.formGroup.get('code').value).subscribe({
    next: () => {
      this.state = VerifyState.SUCCESS;
    },
    error: (err) => {
      if (err.status === 'some_error') {
        this.state = VerifyState.INVALID_OTP;
      }
    },
  });
}

So basically on verify the variable state changes to VERIFYING and after an API call success it should change to SUCCESS . Simple right?

My test looks like this...

it('should set state to VERIFYING and then SUCCESS on successful verification', () => {
  const apiService = spectator.inject(ApiService);
  jest.spyOn(apiService, 'verify').mockReturnValue(of(null));

  spectator.component.verify();

  expect(spectator.component.state).toBe(VerifyState.VERIFYING);

  spectator.detectChanges();

  expect(spectator.component.state).toBe(VerifyState.SUCCESS);
});

In theory this should work, but I add a console.log inside the method subcriptions but it is not logged. Something is very wrong and I can't figure out what. I tried multiple things, fakeAsync with tick, or fixture.whenStable, nothing makes it work.

I tried multiple ways of providing the service, mocks: [ApiService], or

providers: [{
  provide: ApiService,
  useValue: {
    verify: jest.fn(),
  }
}]

and others.

Am I missing something obvious or ??

2

There are 2 best solutions below

0
AliF50 On

This won't fix your problem but a better way to do the VERIFYING and SUCCESS state would be to use a Subject because of(null); can emit instantly and we won't have the opportunity to see that it is VERIFYING.

I have added some comments for stuff that may work.

Something like this:

// Wrap in fakeAsync
it('should set state to VERIFYING and then SUCCESS on successful verification', fakeAsync(() => {
  const apiService = spectator.inject(ApiService);
  const verifySubject = new Subject<VerifyState>();
  // return the observable of the subject. 
  // Change mockReturnValue to mockImplementation to see if
  // it makes a difference.
  jest.spyOn(apiService, 'verify').mockImplementation(() => verifySubject.asObservable());

  spectator.component.verify();
  tick();
  
  // Now it should be verifying since the subject has not emitted.
  expect(spectator.component.state).toBe(VerifyState.VERIFYING);
  
  // Modify the subject
  verifySubject.next(VerifyState.SUCCESS);
  spectator.detectChanges();
  tick();

  expect(spectator.component.state).toBe(VerifyState.SUCCESS);
}));

To debug why it does not go inside of your subscribe, make sure that the const apiService is the same ApiService in the component.

0
Frederico Jesus On

I figure out the problem :/

I was providing the service directly in the component not using provideIn: 'root', so in my tests my mock needs to live in the componentProviders instead of just providers .

I was indeed missing something obvious :(