1
votes

I have two components and a service layer. The service layer has a subject declared in it with a set and get method definition. The first component, based on an user action calls the set method of the subject while the second component is subscribed to the same subject. It works just fine but when I try to unit test the subscription in second component, I'm having trouble with the approach. I have a mock service layer created to test other functionalities as well.

The subscription inside ngOnit is not getting hit during the test run. Code executes within the subscribe when component initializes but not after setSubject is invoked. When I debug, I can see that it hits after the execution is complete but I have no way to verify if data is updated.

export class MockService {
 private subject = new Subject<any>();

 getSubject() {
   return Observable.of(this.subject);
 }

 setSubject(value) {
   this.subject.next({ message: value });
 }

}

Component 2

export class Component2 implements OnInit {
  textFromComponent1: string;
  subscription: Subscription;

  constructor(public service: Service) {}
  ngOnInit() {
    this.subscription = this.service.getSubject().subscribe(message => {
      this.textFromComponent1 = message.value;
    });
  }
}

Component 2 Unit Test

beforeEach( async(() => {
    TestBed.configureTestingModule( {
        declarations: [Component2],
        schemas: [CUSTOM_ELEMENTS_SCHEMA],
        providers: [{ provide: Service, useClass: MockService }]
    } )
        .compileComponents();
} ) );
beforeEach(() => {
    fixture = TestBed.createComponent( Component2 );
    component = fixture.componentInstance;
    fixture.detectChanges();
});
it('should test subscription' () => {
   component.service.setSubject("abc");
   expect(component.textFromComponent1).toBe("abc");
});
1

1 Answers

0
votes

Since a Subject is a special type of Observable, so you should return this.subject from the getSubject() method without wrapping it to an observable using the Observable.of() method.

Here is a working example using angular v11+:

example.component.ts:

import { Component, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { Service } from './example.service';

@Component({})
export class Component2 implements OnInit {
  textFromComponent1: string;
  subscription: Subscription;

  constructor(public service: Service) {}
  ngOnInit() {
    console.log('ngOnInit');
    this.subscription = this.service.getSubject().subscribe((data) => {
      console.log(data);
      this.textFromComponent1 = data.message;
    });
  }
}

example.service.ts:

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable()
export class Service {
  private subject = new Subject<{ message: string }>();

  getSubject() {
    return this.subject;
  }

  setSubject(value: string) {
    this.subject.next({ message: value });
  }
}

example.component.spec.ts:

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Subject } from 'rxjs';
import { Component2 } from './example.component';
import { Service } from './example.service';

class MockService {
  private subject = new Subject<any>();

  getSubject() {
    return this.subject;
  }

  setSubject(value: string) {
    this.subject.next({ message: value });
  }
}

fdescribe('52263178', () => {
  let fixture: ComponentFixture<Component2>;
  let component: Component2;
  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [Component2],
        schemas: [CUSTOM_ELEMENTS_SCHEMA],
        providers: [{ provide: Service, useClass: MockService }],
      }).compileComponents();
    })
  );
  beforeEach(() => {
    fixture = TestBed.createComponent(Component2);
    component = fixture.componentInstance;
  });

  it(
    'should test subscription',
    waitForAsync(() => {
      expect(component.textFromComponent1).toBeUndefined();
      fixture.detectChanges();
      component.service.setSubject('abc');
      fixture.whenStable().then(() => {
        expect(component.textFromComponent1).toBe('abc');
      });
    })
  );
});

unit test result:

✔ Browser application bundle generation complete.
LOG: 'ngOnInit'
Chrome Headless 80.0.3987.87 (Mac OS 10.13.6): Executed 0 of 33 SUCCESS (0 secs / 0 secs)
LOG: Object{message: 'abc'}
Chrome Headless 80.0.3987.87 (Mac OS 10.13.6): Executed 0 of 33 SUCCESS (0 secs / 0 secs)
Chrome Headless 80.0.3987.87 (Mac OS 10.13.6): Executed 2 of 33 (skipped 31) SUCCESS (0.152 secs / 0.071 secs)
TOTAL: 2 SUCCESS

test coverage:

enter image description here