1

Inside a function (ngOnInit in this case) I'm subscribing to an observable. When I get new data, I update another function with that data:

 
ngOnInit(): void {
    this.observable$.subscribe({
      next: (data) => {
        this.function(data)
      },
    });
  }
 
function(data): void {
  console.log(data)
}

The problem comes when I need to test this. I've tried the approaches of all online answers I could find, such as In Jest, how can I unit test a method that subscribes to an observable (I don't have a service I'm mocking) and Angular - Function inside subscribe never called in unit test (not Jest), and below is my current attempt:

 let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
 
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [Modules],
      providers: [provideMock({})],
    }).compileComponents();
  });
 
  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    component['observable$'] = { subscribe: jest.fn() } as unknown as Observable<Type>;
    fixture.detectChanges();
  });
 
describe('ngOnInit', () => {
    it('should call function with Data', (done) => {
      const holdingsAndCashAccounts = [mockHolding(), mockCashAccount()];
      jest.spyOn(component, 'function')
 
      component['observable$'] = of(observableContent);
 
      component['observable$'].subscribe({
        next: (data) => {
          expect(component.function).toHaveBeenCalledWith(data);
          done();
        },
      });
    });
  });

But I get numberOfCalls: 0 from this one.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Hyena
  • 49
  • 1
  • 8
  • Why are you subscribing to the observable in the test, or spying on the thing you're supposed to be testing? And why isn't the test double a real observable, as opposed to just an object with a subscribe property? Test the _behaviour_: given an observable emitting a value, does that value get logged? – jonrsharpe Sep 15 '21 at 20:41
  • 1
    @jonrsharpe Thank you for replying. I have been at this for hours, this is merely my latest attempt - but what I am trying to test is however the function gets called with the data. I'm not quite sure what you are suggesting that I do, any chance you should rephrase it for me or give an example? – Hyena Sep 15 '21 at 21:18

1 Answers1

0

You can use a subject in your tests to have some better control and provide it with .asObservable for the observable in question. You can then call .next with whatever value you want from your tests.

So in your beforeEach before your fixture.detectChanges() something like this

testSubject = new Subject<any>();
component.observable$ = testSubject.asObservable();
fixture.detectChanges();

And then in your test something like this

  spyOn(component, 'function'); //This is for Jasmine, jest.spyOn should work as well
  expect(component.function).toHaveBeenCalledTimes(0);
  const testData = { prop1: 1, prop2: '2' };
  testSubject.next(testData);
  expect(component.function).toHaveBeenCalledTimes(1);
  expect(component.function).toHaveBeenCalledWith(testData);

Here is an example on stackblitz using Jasmine(since I couldn't find a similar project to fork using Jest). Here that you can see I just provide it as a public field of the component.

https://stackblitz.com/edit/jasmine-in-angular-xdsfqk?file=src%2Fapp%2Fapp.component.spec.ts

It looks like maybe your observable is private in which case I would recommend mocking however it is created.

As mentioned it is good to test behavior and if you can you could make your functions private. But I do think there is value in spying on public functions you expect to be called even within the same class and in turn having more granular tests for those function.

Faz
  • 194
  • 1
  • 6