Testing observable object angular 2 karma

11.3k views Asked by At

I'm working on my unit test cases for Angular 2 with Karma, I got stuck with one of a function where I run the test for below line

expect(component.subscribeToEvents()).toBeTruthy();

and I view my coverage code, the lines inside the test file seems not covering anything inside the subscribe. I have tried using MockBackend in mocking the api call inside a function on service but I'm not sure how to do the mocking on a subscribed object, can somebody please help me?

The below is in test.component.ts

subscribeToEvents() {
this.subscription = this.czData.$selectedColorZone
  .subscribe(items => {
    this.resourceLoading = true;
    if (!this.resourceData || (this.resourceData && this.resourceData.length === 0)) {
      this.settings.layout.flypanel.display = false;
      this.getAllResources(this.pagination.start, this.pagination.size);
    }
    else {
      this.pagination.start = 1;
      this.pagination.end = this.pagination.size;
      this.getAllResources(1, this.pagination.size);
      this.settings.layout.flypanel.display = true;
    }
  });
return true;

}

The screenshot of the coverage code enter image description here

2

There are 2 answers

2
Paul Samsotha On

You can't do this, as the subscription is resolved asynchronously. So the synchronous test completes before the async task is resolved.

If all you want is coverage, you can just make the test async. This will cause the Angular test zone to wait until the async task is resolved, before completing the test

import { async } from '@angular/core/testing';

it('..', async(() => {
  component.subscribeToEvents();
}))

You can't try to expect anything here, as there is no callback hook for when the task is resolved. So this is really a pointless test. It will give you coverage, but you aren't actually testing anything. For instance, you might want to test that the variables are set when the subscription is resolved.

Based on the code provided, what I would do instead is just mock the service, and make it synchronous. How can you do that? We you can make the mock something like

class CzDataSub {
  items: any = [];

  $selectedColorZone = {
    subscribe: (callback: Function) => {
      callback(this.items);
    }
  }
}

Then just configure it in the test

let czData: CzDataStub;

beforeEach(() => {
  czData = new CzDataStub();
  TestBed.configureTestingModule({
    providers: [
      { provide: CzData, useValue: czData }
    ]
  })
})

Now in your tests, you don't need to make it async, and you can provide any value you want by just setting the items property on the mock, and subscriber will get it

it('..', () => {
  czData.items = something;
  component.subscribeToEvents();
  expect(component.settings.layout.flypanel.display).toBe(false);
})

UPDATE

I think I was half asleep when I wrote this post. One of the above statements is incorrect

You can't try to expect anything here, as there is no callback hook for when the task is resolved.

This is not completely true. This is what fixture.whenStable() is for. For instance if this is your service

class CzData {
  _value = new Subject<>();

  $selectedColorZone = this._value.asObservable();

  setValue(value) {
    this._value.next(value);
  }
}

Then this is how you would make the test work

let czData: CzData;
let fixture: ComponentFixture<YourComponent>;
let component: YourComponent;

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [ CzData ],
    declarations: [ YourComponent ]
  });
  fixture = TestBed.createComponent(YourComponent);
  component = fixture.componentInstance;
  czData = TestBed.get(czData);
})

it('..', async(() => {
  component.subscribeToEvents();
  czData.setValue(somevalue);
  fixture.whenStable().then(() => {
    expect(component.settings.layout.flypanel.display).toBe(false);
  })
}))

We use fixture.whenStable() to to wait for the async tasks to complete.

This is not to say that using the mock is wrong. A lot of the time, using the mock would be the way to go. I just wanted to correct my statement, and show how it could be done.

0
RocketKittens On

Consider how Angular Outputs are tested since they are subscribed to during testing: https://angular.io/guide/testing#clicking

it('should raise selected event when clicked (triggerEventHandler)', () => {
  let selected: Hero;
  comp.selected.subscribe((hero: Hero) => selectedHero = hero);

  heroDe.triggerEventHandler('click', null);
  expect(selectedHero).toBe(expectedHero);
});

So try:

const expectedItem = {}; // mock the expected result from 'subscribeToEvents'

it('should raise selected event when clicked (triggerEventHandler)', () => {
  let selectedItem: any; // change to expected type
  component.subscribeToEvents.subscribe((item: any) => selectedItem = item);
  // fixture.detectChanges(); // trigger change detection if necessary here, depending on what triggers 'subscribeToEvents'
  expect(selectedItem).toBe(expectedItem);
});