Search code examples
angularunit-testingobservablekarma-jasminebehaviorsubject

How to spyon an observable property


I would like to unit test an Angular 10 component. In the component's ngOnInit method, this code appears:

this.subscriptions.push(
   this.myService.getDataSubject.subscribe((results) => {
      this.data = results;
   }
);

Also, in the unit test

beforeEach(async(() => {
    TestBed.configureTestingModule({
        providers: [MyService]
    }).compileComponents();
})

My unit test includes

beforeEach(() => {
    spyOnProperty(myService, 'getDataSubject', 'get').and.returnValue(of(mockData));
}

In the service itself

private dataSubject: BehaviorSubject<any> = new BehaviorSubject({});
getDataSubject = this.dataSubject.asObservable();

The unit test fails. I've tried multiple variations but cannot get the mockData value to be returned. When writing a unit test for a component that subscribes to a property tied to a BehaviorSubject, what am I supposed to do?


Solution

  • It's a bit confusing topic, but there are two options (and it's definately not using .and.callFake(()=>of(MockData))):

    To return some mock data from service property, it's better to use SpyObject (to avoid providers mess from your service). Or you can create a fully mocked service with duplicated methods and properties.

    In your case, while using SpyObject, to get mocked value you can try this:

    describe('Using observable property', () => {
      let service: ServiceClassName;
    
      beforeEach(async () => {
        const serviceSpy = jasmine.createSpyObj('ServiceClassName', [
          // properties and methods list
          'observable$'
        ]);
    
        await TestBed.configureTestingModule({
          providers: [
            {provide: ServiceClassName, userValue: serviceSpy}
          ]
        }).compileComponents();
      });
    
      beforeEach(() => {
        service = TestBed.inject(ServiceClassName);
    
        // actual defining mock data
        service.observable$ = of({}); // empty object as mock data
      });
    
      it('test case', () => {
        service.observable$.subscribe(value => {
          console.log(value); // {}
        });
      });
    });
    
    

    In fact, you don't need to subscribe to observable property, it's just for example. Also, in case your component subscribed from service observable$ it would also get this mocked value