如何用单元测试覆盖 rxjs 订阅方法?
How can I cover rxjs subscribe method with unit tests?
我正在尝试编写单元测试来覆盖我的每一行代码。我有两行代码没有涉及。
我不明白我在哪里犯了错误以及如何覆盖这些代码行?
这是未覆盖代码行的图片:
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;
}
看起来您在执行过程中创建间谍对象的时间太晚了。如果您在调用 productService.delete 之前创建了间谍对象,但将 expect 断言保留在原处,那应该可以解决问题。
尝试为您的 fetchDataEventEmitter
属性 分配一个简单的可观察对象并将其移至案例顶部:
it('should call delete method', () => {
component.fetchDataEventEmitter = of({})
component.onDelete();
fixture.detectChanges();
productService.deleteProduct(component.selectedItemId).subscribe(selectedItemId => {
expect(selectedItemId).toBeTruthy();
})
});
这里最后一个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();
});
我正在尝试编写单元测试来覆盖我的每一行代码。我有两行代码没有涉及。
我不明白我在哪里犯了错误以及如何覆盖这些代码行?
这是未覆盖代码行的图片:
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;
}
看起来您在执行过程中创建间谍对象的时间太晚了。如果您在调用 productService.delete 之前创建了间谍对象,但将 expect 断言保留在原处,那应该可以解决问题。
尝试为您的 fetchDataEventEmitter
属性 分配一个简单的可观察对象并将其移至案例顶部:
it('should call delete method', () => {
component.fetchDataEventEmitter = of({})
component.onDelete();
fixture.detectChanges();
productService.deleteProduct(component.selectedItemId).subscribe(selectedItemId => {
expect(selectedItemId).toBeTruthy();
})
});
这里最后一个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();
});