使用可观察的声明性方法使用异步测试 angular 组件时获取 TypeError 管道不是函数
Getting TypeError pipe is not a function while testing angular component using observable with declarative approach using async
When run the tests i am getting i am getting the "TypeError: this.dashboardService.serviceAgents$.pipe is not a function" error.
- ServiceDashboard 页面代码如下:
import { Component } from '@angular/core';
import { ServiceDashboardService } from '../services/service-dashboard.service';
import { tap } from 'rxjs/operators';
import { ServiceAgent } from '../interfaces/service-agent';
@Component({
selector: 'app-service-dashboard',
templateUrl: './service-dashboard.page.html',
styleUrls: ['./service-dashboard.page.css'],
})
export class ServiceDashboardPage {
serviceAgentSlideOptions: any = {
slidesPerView: 4
};
serviceAgents$ = this.dashboardService.serviceAgents$
.pipe(
tap(serviceAgents => {
this.serviceAgentSlideOptions.slidesPerView = serviceAgents.length < 4 ? serviceAgents.length : 4;
})
);
constructor(private dashboardService: ServiceDashboardService) { }
}
- 以下是 ServiceDashboardPage 的单元测试代码。
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ServiceDashboardPage } from './service-dashboard.page';
import { ServiceDashboardService } from '../services/service-dashboard.service';
import { ServiceAgent } from '../interfaces/service-agent';
import { of } from 'rxjs';
describe('ServiceDashboardPage', () => {
let component: ServiceDashboardPage;
let fixture: ComponentFixture<ServiceDashboardPage>;
let serviceDashboardServiceSpy: ServiceDashboardService;
beforeEach(async(() => {
serviceDashboardServiceSpy = jasmine.createSpyObj('ServiceDashboardService',
['serviceAgents$']);
TestBed.configureTestingModule({
declarations: [ServiceDashboardPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: ServiceDashboardService, useValue: serviceDashboardServiceSpy }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ServiceDashboardPage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', async(() => {
(serviceDashboardServiceSpy.serviceAgents$ as unknown as
jasmine.Spy).and.returnValue(of([] as ServiceAgent[]));
expect(component).toBeTruthy();
}));
});
您编写的代码存在一些问题。正如上面评论中指出的那样,您显然希望服务中有一个 Observable,但是命令:
serviceDashboardServiceSpy = jasmine.createSpyObj('ServiceDashboardService', ['serviceAgents$']);
将创建 serviceAgents$
作为函数,而不是 Observable。
但是仅仅更改它不会使您的代码可测试,因为您将想要更改该 Observable 返回的值并进行测试以查看您的组件是否对这些更改做出正确的反应。为此,您需要重构代码。重构的原因是因为您通过立即定义在组件中设置 Observable 的方式意味着很难更改值和轻松测试。然而,简单地将分配移动到 ngOnInit()
将使这更容易测试。
然后,您需要将 fixture.detectChanges()
移出 beforeEach()
并移入规范本身(it()
函数)。这样做的原因是因为 fixture.detectChanges()
将执行我们刚刚设置的 ngOnInit()
,我们希望更仔细地控制何时调用它。
最后,您需要设置一些东西来模拟您的服务 class - 您试图使用 serviceDashboardServiceSpy
对象来这样做,但在这种情况下我更喜欢使用模拟 class 而不是间谍对象。这是因为您在实际服务 class 中将 serviceAgents$
定义为 属性 而不是函数。这使得测试变得更加棘手,并且在我看来设置模拟 class 而不是间谍对象会让这更容易一些。
考虑到所有这些因素,我设置此 StackBlitz 以显示您的测试通过。
我还添加了几个测试来展示更改服务 Observable 中的值如何更改组件中的关联值。
这是来自 StackBlitz 的 .spec:
class MockServiceDashboardService {
get serviceAgents$() {
return of({length: 2});
}
}
describe('ServiceDashboardPage', () => {
let component: ServiceDashboardPage;
let fixture: ComponentFixture<ServiceDashboardPage>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ServiceDashboardPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: ServiceDashboardService, useClass: MockServiceDashboardService }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ServiceDashboardPage);
component = fixture.componentInstance;
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it('should have length of 2', () => {
fixture.detectChanges();
expect(component.serviceAgentSlideOptions.slidesPerView).toEqual(2);
});
it('should have a length of 3', () => {
let dService = TestBed.get(ServiceDashboardService);
spyOnProperty(dService, 'serviceAgents$').and.returnValue(of({length: 3}))
fixture.detectChanges();
expect(component.serviceAgentSlideOptions.slidesPerView).toEqual(3);
});
});
When run the tests i am getting i am getting the "TypeError: this.dashboardService.serviceAgents$.pipe is not a function" error.
- ServiceDashboard 页面代码如下:
import { Component } from '@angular/core';
import { ServiceDashboardService } from '../services/service-dashboard.service';
import { tap } from 'rxjs/operators';
import { ServiceAgent } from '../interfaces/service-agent';
@Component({
selector: 'app-service-dashboard',
templateUrl: './service-dashboard.page.html',
styleUrls: ['./service-dashboard.page.css'],
})
export class ServiceDashboardPage {
serviceAgentSlideOptions: any = {
slidesPerView: 4
};
serviceAgents$ = this.dashboardService.serviceAgents$
.pipe(
tap(serviceAgents => {
this.serviceAgentSlideOptions.slidesPerView = serviceAgents.length < 4 ? serviceAgents.length : 4;
})
);
constructor(private dashboardService: ServiceDashboardService) { }
}
- 以下是 ServiceDashboardPage 的单元测试代码。
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ServiceDashboardPage } from './service-dashboard.page';
import { ServiceDashboardService } from '../services/service-dashboard.service';
import { ServiceAgent } from '../interfaces/service-agent';
import { of } from 'rxjs';
describe('ServiceDashboardPage', () => {
let component: ServiceDashboardPage;
let fixture: ComponentFixture<ServiceDashboardPage>;
let serviceDashboardServiceSpy: ServiceDashboardService;
beforeEach(async(() => {
serviceDashboardServiceSpy = jasmine.createSpyObj('ServiceDashboardService',
['serviceAgents$']);
TestBed.configureTestingModule({
declarations: [ServiceDashboardPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: ServiceDashboardService, useValue: serviceDashboardServiceSpy }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ServiceDashboardPage);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', async(() => {
(serviceDashboardServiceSpy.serviceAgents$ as unknown as
jasmine.Spy).and.returnValue(of([] as ServiceAgent[]));
expect(component).toBeTruthy();
}));
});
您编写的代码存在一些问题。正如上面评论中指出的那样,您显然希望服务中有一个 Observable,但是命令:
serviceDashboardServiceSpy = jasmine.createSpyObj('ServiceDashboardService', ['serviceAgents$']);
将创建 serviceAgents$
作为函数,而不是 Observable。
但是仅仅更改它不会使您的代码可测试,因为您将想要更改该 Observable 返回的值并进行测试以查看您的组件是否对这些更改做出正确的反应。为此,您需要重构代码。重构的原因是因为您通过立即定义在组件中设置 Observable 的方式意味着很难更改值和轻松测试。然而,简单地将分配移动到 ngOnInit()
将使这更容易测试。
然后,您需要将 fixture.detectChanges()
移出 beforeEach()
并移入规范本身(it()
函数)。这样做的原因是因为 fixture.detectChanges()
将执行我们刚刚设置的 ngOnInit()
,我们希望更仔细地控制何时调用它。
最后,您需要设置一些东西来模拟您的服务 class - 您试图使用 serviceDashboardServiceSpy
对象来这样做,但在这种情况下我更喜欢使用模拟 class 而不是间谍对象。这是因为您在实际服务 class 中将 serviceAgents$
定义为 属性 而不是函数。这使得测试变得更加棘手,并且在我看来设置模拟 class 而不是间谍对象会让这更容易一些。
考虑到所有这些因素,我设置此 StackBlitz 以显示您的测试通过。
我还添加了几个测试来展示更改服务 Observable 中的值如何更改组件中的关联值。
这是来自 StackBlitz 的 .spec:
class MockServiceDashboardService {
get serviceAgents$() {
return of({length: 2});
}
}
describe('ServiceDashboardPage', () => {
let component: ServiceDashboardPage;
let fixture: ComponentFixture<ServiceDashboardPage>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ServiceDashboardPage],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: ServiceDashboardService, useClass: MockServiceDashboardService }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ServiceDashboardPage);
component = fixture.componentInstance;
});
it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});
it('should have length of 2', () => {
fixture.detectChanges();
expect(component.serviceAgentSlideOptions.slidesPerView).toEqual(2);
});
it('should have a length of 3', () => {
let dService = TestBed.get(ServiceDashboardService);
spyOnProperty(dService, 'serviceAgents$').and.returnValue(of({length: 3}))
fixture.detectChanges();
expect(component.serviceAgentSlideOptions.slidesPerView).toEqual(3);
});
});