14

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();
    });

});
Lin Du
  • 88,126
  • 95
  • 281
  • 483
TheRyanSmee
  • 231
  • 1
  • 2
  • 9

2 Answers2

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)

rmcsharry
  • 5,363
  • 6
  • 65
  • 108
TheRyanSmee
  • 231
  • 1
  • 2
  • 9
  • 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. – Qarun Qadir Bissoondial Jan 07 '21 at 13:52
5

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

Lin Du
  • 88,126
  • 95
  • 281
  • 483
  • Hi, thank you for your response. I Am now getting the error `TypeError: observer is not a function`. – TheRyanSmee Nov 12 '19 at 15:19
  • @TheRyanSmee Please check your code. The code of my answer works fine. – Lin Du Nov 12 '19 at 16:35
  • 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 { return of( true ); }` Do you know how to write a test for the method returning an Observable. – TheRyanSmee Nov 12 '19 at 18:14
  • 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)` – Qarun Qadir Bissoondial May 05 '21 at 15:14