Nikolay
Nikolay

Reputation: 59

How can I cover rxjs subscribe method with unit tests?

I`m trying to write unit tests to cover every line of my code. I have two lines of my code that are not covered.

I can`t understand where I make a mistake and how I can cover these lines of code?

Here is an image of not covered lines of code:

code coverage screenshot

Product-item.spect.ts

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture,TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ProductService } from '../../services/product.service';
import { ProdItemComponent } from './product-item.component';

describe('ProdItemComponent', () => {
  let component: ProdItemComponent;
  let fixture: ComponentFixture<ProdItemComponent>;
  let productService: ProductService;
  let mockProductItem: any;

  beforeEach(async(() => {

    TestBed.configureTestingModule({
      declarations: [ProductItemComponent],
      imports: [HttpClientTestingModule, RouterTestingModule],
      providers: [ProductService],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
      .compileComponents();
  }));

  beforeEach(() => {

    mockProductItem= {
      id: "c7336f01-5219-4631-a865-af1fa9837766",
      title:"Carpet",
      description:"A soft Carpet"
    }

    fixture = TestBed.createComponent(ProductItemComponent);
    component = fixture.componentInstance;
    productService = TestBed.inject(ProductService);
    fixture.detectChanges();
    component.productItem = mockProductItem;
    component.selectedItemId = component.mockProductItem.id;

  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should call delete method', () => {
    component.onDelete();
    fixture.detectChanges();
    productService.deleteProduct(component.selectedItemId).subscribe(selectedItemId => {
      expect(selectedItemId).toBeTruthy();
      const spy = spyOn(component.fetchDataEventEmitter, 'emit');
      expect(spy).toHaveBeenCalled();
    })
  });
  expect(component.displayConfirmDialog).toBe(false);
});

Product-service.ts

 deleteProduct(productId: string) {
    return this.httpClient.delete(
      this.BASE_URL +
      this.DELETE_ITEM_URL(
        productId
      )
    )
  }

Product-component.ts

onDelete(): void {
    if (this.selectedItemId) {
      this.productService.deleteProduct(this.selectedItemId).subscribe(res => {
        this.fetchDataEventEmitter.emit();
      });
    }
    this.displayConfirmDialog = false;
  }

Upvotes: 2

Views: 8719

Answers (3)

AliF50
AliF50

Reputation: 18809

The last expect here is out of place, it should be moved one line up.

  it('should call delete method', () => {
    component.onDelete();
    fixture.detectChanges();
    productService.deleteProduct(component.selectedItemId).subscribe(selectedItemId => {
      expect(selectedItemId).toBeTruthy();
      const spy = spyOn(component.fetchDataEventEmitter, 'emit');
      expect(spy).toHaveBeenCalled();
    });
    // should go here
    expect(component.displayConfirmDialog).toBe(false);
  });
  // expect(component.displayConfirmDialog).toBe(false);

You need to mock ProductService using a spyObject because you don't want to be doing actual http calls in your unit tests.

  let component: ProdItemComponent;
  let fixture: ComponentFixture<ProdItemComponent>;
  let productService: ProductService;
  let mockProductItem: any;
  // add this line
  let mockProductService: jasmine.SpyObj<ProductService>;

  beforeEach(async(() => {
    // create a spy object
    // the first string is an identifier and is optional. The array of strings
    // are the public methods that you would like to mock.
    mockProductService = jasmine.createSpyObj<ProductService>('ProductService', ['deleteProduct']);
    TestBed.configureTestingModule({
      declarations: [ProductItemComponent],
      // get rid of HttpClientTestingModule since we are mocking
      // ProductService now
      imports: [/*HttpClientTestingModule*/, RouterTestingModule],
      // when the component asks for ProductService, give the mocked one
      providers: [{ provide: ProductService, useValue: mockProductService }],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
      .compileComponents();
  }));

...
it('should call delete method', () => {
    // make the deleteProduct method return an observable of empty string
    mockProductService.deleteProduct.and.returnValue(of(''));
    // spy on the emission
    const emitSpy = spyOn(component.fetchDataEventEmitter, 'emit');

    component.onDelete();
    fixture.detectChanges();

    expect(emitSpy).toHaveBeenCalled();
    expect(component.displayConfirmDialog).toBeFalse();
  });

Upvotes: 3

Roman A.
Roman A.

Reputation: 742

Try to assign a simple observable to your fetchDataEventEmitter property and move it to the top of the case:

 it('should call delete method', () => {
    component.fetchDataEventEmitter = of({})
    component.onDelete();
    fixture.detectChanges();
    productService.deleteProduct(component.selectedItemId).subscribe(selectedItemId => {
      expect(selectedItemId).toBeTruthy();
    })
  });

Upvotes: 0

byte-this
byte-this

Reputation: 214

It looks like you are creating the spy object too late in the execution. If you create the spy object before calling productService.delete but leave the expect assertion where it is, that should solve the issue.

Upvotes: 0

Related Questions