Search code examples
angularmockingjasminesubject

Angular Jasmine - spy a subject from a mocked service in a component


Hello I'm testing a component and in that component I have CartService with two subjects. Im creating a spyObject for that service. And now when angular tries to subscribe to those subjects they are well undefined. Tried to returnValue of a Subject or something but nothing works for now. Still undefined.

This is the service and the Subjects.

export class CartService {

  totalQuantity: Subject<number> = new Subject<number>();
  totalPrice: Subject<number> = new Subject<number>();
}

Then in the component on ngOnInit() I just subscribe to them. This is the place Angular tries to subscribe but they are undefined and I don't know how to mock them

this.subscribePrice = this.cartService.totalPrice.subscribe(data => this.totalPrice = data);
this.subscribeQuantity = this.cartService.totalQuantity.subscribe(data => this.totalQuantity = data);

Here is the test class:

fdescribe('CheckoutComponent', () => {
  let component: CheckoutComponent;
  let fixture: ComponentFixture<CheckoutComponent>;
  let el: DebugElement;
  let messageToastrService: any;
  let cartService: any;
  let priceSubject: Subject<number>;
  let quantitySubject: Subject<number>;

  beforeEach(async(() => {
    priceSubject = new Subject<number>();
    quantitySubject = new Subject<number>();
    const messageToastrSpy = jasmine.createSpyObj('MessageToastrService', ['success']);
    const cartServiceSpy = jasmine.createSpyObj('CartService',
      ['getCartFromStorage', 'computeTotals'], ['totalQuantity', 'totalPrice']);

    TestBed.configureTestingModule({
      declarations: [ CheckoutComponent ],
      imports: [
        ProductsModule,
        RouterTestingModule.withRoutes([]),
        HttpClientTestingModule,
      ],
      providers: [
        {provide: MessageToastrService, useValue: messageToastrSpy},
        {provide: CartService, useValue: cartServiceSpy},
      ]
    })
    .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(CheckoutComponent);
        component = fixture.componentInstance;
        el = fixture.debugElement;
        messageToastrService = TestBed.inject(MessageToastrService);
        cartService = TestBed.inject(CartService);
        fixture.detectChanges();
      });
  }));

Now just concentrating on the matter at hand. These are the ways I tried to fix the thing. But still, nothing works.

it('should properly initialize component', function() {
    component.ngOnInit();

    // cartService.totalPrice.and.returnValue(priceSubject.asObservable());
    // cartService.totalQuantity.and.returnValue(quantitySubject.asObservable());

    cartService.totalPrice.and.returnValue(of());
    cartService.totalQuantity.and.returnValue(of());

    // (cartService.totalPrice as jasmine.Spy).and.returnValue(priceSubject.asObservable());
    // (cartService.totalQuantity as jasmine.Spy).and.returnValue(quantitySubject.asObservable());
  });

The trace:

Failed: Uncaught (in promise): TypeError: Cannot read property 'subscribe' of undefined
TypeError: Cannot read property 'subscribe' of undefined
    at CheckoutComponent.uploadCartInfo (http://localhost:9876/_karma_webpack_/webpack:/src/app/modules/products/components/checkout/checkout.component.ts:62:55)
    at CheckoutComponent.ngOnInit 
error properties: Object({ rejection: TypeError: Cannot read property 'subscribe' of undefined, promise: [object Promise], zone: Zone({ _parent: Zone({ _parent: null, _name: '<root>', _properties: Object({  }), _zoneDelegate: ZoneDelegate({ _taskCounts: Object({ microTask: 0, macroTask: 0, eventTask: 0 }), zone: <circular reference: Object>, _parentDelegate: null, _forkZS: null, _forkDlgt: null, _forkCurrZone: null, _interceptZS: null, _interceptDlgt: null, _interceptCurrZone: null, _invokeZS: null, _invokeDlgt: null, _invokeCurrZone: null, _handleErrorZS: null, _handleErrorDlgt: null, _handleErrorCurrZone: null, _scheduleTaskZS: null, _scheduleTaskDlgt: null, _scheduleTaskCurrZone: null, _invokeTaskZS: null, _invokeTaskDlgt: null, _invokeTaskCurrZone: null, _cancelTaskZS: null, _cancelTaskDlgt: null, _cancelTaskCurrZone: null, _hasTaskZS: null, _hasTaskDlgt: null, _hasTaskDlgtOwner: null, _hasTaskCurrZone: null }) }), _name: 'ProxyZone', _properties: Object({ ProxyZoneSpec: ProxyZoneSpec({ defaultS ...
Error: Uncaught (in promise): TypeError: Cannot read property 'subscribe' of undefined
TypeError: Cannot read property 'subscribe' of undefined
    at CheckoutComponent.uploadCartInfo (http://localhost:9876/_karma_webpack_/webpack:/src/app/modules/products/components/checkout/checkout.component.ts:62:55)
    at CheckoutComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/modules/products/components/checkout/checkout.component.ts:40:10)
    at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:2922:1)
    at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:2892:1)
    at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:2844:1)
    at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7213:1)
    at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7312:1)
    at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:8507:1)
    at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:8532:1)
    at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:9918:1)
    at resolvePromise (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:798:1)
    at http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:864:1
    at ZoneDelegate.invokeTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:399:1)
    at AsyncTestZoneSpec.onInvokeTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:1016:1)
    at ProxyZoneSpec.onInvokeTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:320:1)
    at ZoneDelegate.invokeTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:398:1)
    at Zone.runTask (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:167:1)
    at drainMicroTaskQueue (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:569:1)

How can I fix this? Thanks.


Solution

  • Found the answer with help from Szymon.

    First i had to change the spyObject of cartServiceSpy and underneath add those two fields to spy them.

     const cartServiceSpy = jasmine.createSpyObj('CartService', ['getCartFromStorage', 'computeTotals']);
     cartServiceSpy.totalQuantity = quantitySubject;
     cartServiceSpy.totalPrice = priceSubject;