简体   繁体   中英

Unit test fails when stream/event has pipe(takeUntil(destroy))

Angular: 9.1.13; RxJS: 6.6.3; Karma: 4.3.0; jasmine-core: 2.6.2

We have a component that has service which does something with streams and events.

Each such component method unsubscribes from stream/event when it is destroyed by means of calling this._destroy.next() , subject declared as private _destroy = new Subject() in ngOnDestroy hook.

Everything seems to be good until we run unit test. Method and test example can be found below.

It is unclear why test fails when subscribed to a service stream/event with takeUntil .

(another use case) not working with stubs returning observables that have pipe(takeUntil(destroy))

Error when running test using jasmine/karma and phantom.js

TypeError: undefined is not a constructor (evaluating 'this.service.getServicePackages().pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_9__["takeUntil"])(this._destroy))')

Method example:

getServicePackages(): void {
 this.service.getServicePackages().pipe(takeUntil(this._destroy)).subscribe(() => ... );
}

Test example:


beforeAll(() => {
  serviceStub = {
    getServicePackages: () => ({ subscribe: () => ({}) }),
    // v2 -> getServicePackages: () => ({ subscribe: (): Observable<TYPE> => of({}) }),
  }
})

it('...', () => {
  const serviceStub = TestBed.inject(PackageService);

  spyOn(serviceStub, 'getServicePackage').and.callThrough();
  spyOn(component, 'getServicePackage').and.callThrough();

  component.getServicePackage();

  expect(component.getServicePackage).toHaveBeenCalled();
  expect(serviceStub.getServicePackage).toHaveBeenCalled();
})

the problem becomes obvious if we put 2 lines togeter

serviceStub = {
    getServicePackages: () => ({ subscribe: () => ({}) }),
    // v2 -> getServicePackages: () => ({ subscribe: (): Observable<TYPE> => of({}) }),
};
// and then in code of component
serviceStub.getServicePackages().pipe(does not matter what); // here it doesn't have pipe method

i would suggest returning the observable itself. And in case you want to test also events - return a subject.

servicePackagesSubject = new Subject();
serviceStub = {
    getServicePackages: jasmine.createSpy('getServicePackages').and.returnValue(servicePackagesSubject),
  }

I think this is the best way to move forward on cases like this:

// As mentioned earlier by @Andrei, it does not matter about the pipe operators. 

// In your test case, create a mock service, return a value, and use it as a useClass method in the providers and simply spyOn your component method. Something like this: 


// In your spec file, 

@Injectable() 
class MockService extends YourOriginalService {
  getServicePackages() {
    // Since you are subscribing, i am assuming this returns an observable, hence
    // the 'of' operator
    
    const mockData = {fill your mockData here};
    return of(mockData);
  }
}

// In your before each, add the mockService to your providers 

let mockService: YourOriginalService;
beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [],
      ....
      providers: [
        {
          provide: YourOriginalService,
          useClass: MockService
        }
      ]
      ......
    }).compileComponents();

    mockService = TestBed.get(YourOriginalService);
  }));



/// Now your test case turns to this: 

it('should call the #getServicePackages() method in component', () => {
  
  spyOn(component, 'getServicePackages').and.callThrough();
  component.getServicePackages();
  expect(component.getServicePackages).toHaveBeenCalled();

});


// Now if you want to test the 'error' part in the subscribe, you can take care of that in this test case like this: 

it('should call the #getServicePackages() method in component, for error scenario', () => {
  // Assuming its an API Call, otherwise replace the status object with your mock error object
  spyOn(component, 'getServicePackages').and.callThrough();
  spyOn(mockService, 'getServicePackages')
    .and.returnValue(throwError(of({status: 500})));
  component.getServicePackages();
  expect(component.getServicePackages).toHaveBeenCalled();

});

// Note: Please don't mix the service success scenario coverage and service error scenario coverage in the same test case. Use 2 test cases like above. 


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM