【问题标题】:How can I cover rxjs subscribe method with unit tests?如何使用单元测试覆盖 rxjs 订阅方法?
【发布时间】:2021-08-19 01:40:52
【问题描述】:

我正在尝试编写单元测试来覆盖我的每一行代码。我有两行代码没有被覆盖。

我不明白我在哪里犯了错误以及如何覆盖这些代码行?

这是未覆盖的代码行的图像:

产品-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
      )
    )
  }

产品组件.ts

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

【问题讨论】:

    标签: angular rxjs angular-unit-test


    【解决方案1】:

    看起来您在执行过程中创建间谍对象为时已晚。如果您在调用 productService.delete 之前创建了 spy 对象,但将期望断言保留在原处,那应该可以解决问题。

    【讨论】:

    • 我把这个“const spy = spyOn(component.fetchDataEventEmitter, 'emit')”移到了顶部,但是这些代码行是一样的,它仍然没有被覆盖。 SPEC 没有期望应该调用删除方法
    【解决方案2】:

    尝试为您的 fetchDataEventEmitter 属性分配一个简单的 observable 并将其移动到案例的顶部:

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

    【讨论】:

    • 当我尝试将一个简单的 observable 分配给 fetchDataEventEmitter 时出现此错误。 - 类型“Observable”缺少类型“EventEmitter”中的以下属性:emit、observers、closed、isStopped 等 7 个。
    • spyOn(component. fetchDataEventEmitter, 'emit'); 不起作用吗?
    【解决方案3】:

    这里最后一个expect不合适,应该上移一行。

      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);
    

    您需要使用 spyObject 模拟 ProductService,因为您不想在单元测试中进行实际的 http 调用。

      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();
      });
    
    

    【讨论】:

    • 谢谢 AliF50。现在我通过了未覆盖的代码行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-28
    • 1970-01-01
    相关资源
    最近更新 更多