In Jest, how can I unit test a method that subscribes to an observable
Asked Answered
T

2

17

I have various methods in my components that subscribe to methods in injected dependencies, that return observables.

I want to write Jest unit tests to ensure that when these observables return / error, my methods do the correct thing.

In the below example I am trying to write a test that checks if doAThing has fired. Neither of the below tests work. They both fail with errors like

'returnMyObservable.subscribe is not a function'.

// Example method to test component 
public testFunction (): void {
    this.myService.returnMyObservable.subscribe(
        ( value ) => this.doAThing( value )
    )
}
describe( 'myComponemt', () => {

    let fixture;
    let myServiceMock;

    beforeEach( () => {
        myServiceMock = {
            returnMyObservable: fn()
        }

        fixture = new myComponent( myServiceMock );
    });


    // 1) I have tried mocking with a returned value
    it ( 'should call do a thing when value is returned', () => {
        myServiceMock.returnMyOnservable.mockReturnValue( true );

        fixture.testFunction();

        expect( fixture.doAThing ).toHaveBeenCalled();
    });

    // 2) I have tried returning an observable
    it ( 'should call do a thing when value is returned', () => {
        myServiceMock.returnMyOnservable.mockReturnValue( of( true ) );

        fixture.testFunction();

        expect( fixture.doAThing ).toHaveBeenCalled();
    });

});
Transfuse answered 12/11, 2019 at 9:18 Comment(0)
T
9

I had a few other errors else where that were masking what was actually wrong with my tests - I have found that the best way to test the above functionality is with:

describe( 'MyComponent', () => {
    let fixture;
    let myServiceMock;

    beforeEach( () => {
        myServiceMock = {
            returnMyObservable: jest.fn()
        }

        fixture = new MyComponent( myServiceMock );
    });

    it ( 'should call doAThing when value is returned', () => {
        const doAThingSpy = jest.spyOn( fixture, 'doAThing' );
        myServiceMock.returnMyObservable.mockReturnValue( of( true ) );

        fixture.testFunction();

        expect( doAThingSpy ).toHaveBeenCalledWith( true );
    });
});

(This is pretty much the same way to do it in Jasmine too)

Transfuse answered 20/11, 2019 at 9:57 Comment(1)
I had a slightly different approach, though I still feel it's somewhat incorrect. I mocked the service function to return an observable of my choosing using of(value). I just called the component that executes the subscription inside of it, tested the doAThing function with toHaveBeenCalled() and added done() at the end of the test case. All my tests seem to pass with this setup.Gaseous
C
6

We can use mockImplementation to mock the implementation of this.myService.returnMyObservable. After that, we can get the function you passed in subscribe in the test case(observer), then execute it manually.

Here is the solution:

index.ts:

export class MyComponent {
  private myService;
  constructor(myService) {
    this.myService = myService;
  }
  public testFunction(): void {
    this.myService.returnMyObservable.subscribe(value => this.doAThing(value));
  }
  public doAThing(value) {}
}

index.spec.ts:

import { MyComponent } from './';

describe('MyComponent', () => {
  let fixture;
  let myServiceMock;

  beforeEach(() => {
    myServiceMock = {
      returnMyObservable: {
        subscribe: jest.fn()
      }
    };
    fixture = new MyComponent(myServiceMock);
  });

  it('should call do a thing when value is returned', () => {
    let observer;
    myServiceMock.returnMyObservable.subscribe.mockImplementation(handler => {
      observer = handler;
    });
    jest.spyOn(fixture, 'doAThing');
    fixture.testFunction();
    observer();

    expect(fixture.doAThing).toHaveBeenCalled();
  });
});

Unit test result with 100% coverage:

 PASS  src/stackoverflow/58815471/index.spec.ts (7.367s)
  MyComponent
    ✓ should call do a thing when value is returned (5ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.773s

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/58815471

Clavichord answered 12/11, 2019 at 12:15 Comment(4)
Hi, thank you for your response. I Am now getting the error TypeError: observer is not a function.Transfuse
@Transfuse Please check your code. The code of my answer works fine.Clavichord
Thanks again for your patience. I can see why mine is failing and yours is not. In your example, you are subscribing to an Observable in the service. I am subscribing to an Observable that is returned from a method. If you change your service to match the following code you will see the error. public returnMyObservable (): Observable<boolean> { return of( true ); } Do you know how to write a test for the method returning an Observable.Transfuse
I should probably add that if doAThing has functions inside of it that require the value from the subscribe block, then observer should be called with that parameter. observer(value)Gaseous

© 2022 - 2024 — McMap. All rights reserved.