Testing observable object angular 2 karma

10,801

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.

Share:
10,801
Demona
Author by

Demona

I'm a web developer and I like to work on new web technologies.

Updated on June 15, 2022

Comments

  • Demona
    Demona almost 2 years

    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

  • Demona
    Demona over 7 years
    I tried the above and my test fails due to the { provide: CzData: useClass: czData } , any idea why?
  • Paul Samsotha
    Paul Samsotha over 7 years
    Sorry it should be useValue instead of useClass. And make sure the provide is actually your class. I couldn't see the name of your class,so I just assumed that's what it was. And it should be a comma instead of colon. I think that's the error. See the updated code