Search code examples
angularunit-testingpropertiesjasminesubject

Mocking a Subject property of mocked service to be subscribed in the Angular unit test


In my Angular unit testing my mocked service has two properties:

  public messageChange: Subject<ChatMessage> = new Subject<ChatMessage>();
  public gameChange: Subject<GameState> = new Subject<GameState>();

And in my SUT I use them in my constructor:

this._subChat = this.hub.messageChange.subscribe((message: ChatMessage) => {
      this.message = message;
      this.messageChange.next(this.message);
    });
    this._subGame = this.hub.gameChange.subscribe((game: GameState) => {
      this.game.setGame(game);
    });

Right now I am trying to use these 2 approaches to mock properties from mocked service:

Riht now my testing looks like this:

describe('SignalRService', () => {
  let signalrService: SignalRService;
  const hubServiceMock = jasmine.createSpyObj('HubConnectionService', [
    'isConnectionStarted',
  ]);
  const messageChange = new Subject();
  const gameChange = new Subject();

  beforeEach(() => {
    signalrService = new SignalRService(
      hubServiceMock
    );
    let msg = {} as ChatMessage;
    let gam = {} as GameState;
    messageChange.next(msg);
    gameChange.next(gam);
  });

  it('Service_ShouldBeCreated', () => {
    spyOnProperty(hubServiceMock, 'messageChange', 'get').and.returnValue(
      messageChange
    );
    spyOnProperty(hubServiceMock, 'gameChange', 'get').and.returnValue(
      gameChange
    );
    expect(signalrService).toBeTruthy();
  });
}

So in the test I create:

  • service mock hubServiceMock,

  • fake messageChange = new Subject();,

  • fake gameChange = new Subject();,

  • i run for both .next,

  • and I setup spy for properties:

    spyOnProperty(hubServiceMock, 'messageChange', 'get').and.returnValue( messageChange );

    spyOnProperty(hubServiceMock, 'gameChange', 'get').and.returnValue( gameChange );

Why it does not work to me? I receive an error:

Cannot read property 'subscribe' of undefined


Solution

  • It doesn't work because hubServiceMock doesn't have the fake subjects in its messageChange and gameChange, you need to set them before calling new SignalRService(hubServiceMock).

      const hubServiceMock = jasmine.createSpyObj('HubConnectionService', [
        'isConnectionStarted',
      ]);
      const messageChange = new Subject();
      const gameChange = new Subject();
    
      // add this
      hubServiceMock.messageChange = messageChange;
      hubServiceMock.gameChange = gameChange;
    

    then it should work, maybe small adjustments are needed.


    I would suggest to use a mocking lib for such cases to avoid pain.

    For example with ng-mocks, the test might look like:

    describe('SignalRService', () => {
      beforeEach(() => MockBuilder(SignalRService, ITS_MODULE));
    
      const hubServiceMock = {
        messageChange: new Subject(),
        gameChange: new Subject(),
      };
      beforeEach(() => MockInstance(HubConnectionService, hubServiceMock));
    
    
      it('Service_ShouldBeCreated', () => {
        const signalrService = MockRender(SignalRService).point.componentInstance;
        expect(signalrService).toBeTruthy();
    
        hubServiceMock.messageChange.next({});
        hubServiceMock.gameChange.next({});
        // next assertions.
      });
    }