模拟服务与依赖项
通过 DI 提供测试替身
模拟服务与依赖项 是 CoddyKit 上的免费 Angular Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Angular Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Angular Academy 课程共包含 4 节课。
为什么要模拟依赖
组件测试应当隔离组件。如果组件注入了基于 HttpClient 的服务,您不会希望它发起真实的网络请求。相反,请提供一个测试替身:一个返回可控数据的伪造对象,使测试快速且结果确定。
使用 useValue 提供伪造对象
最简单的测试替身就是使用 useValue 提供的普通对象。请使用组件所请求的同一个注入令牌来提供它。
const fakeApi = { getUser: () => of({ name: 'Ada' }) };
TestBed.configureTestingModule({
imports: [ProfileComponent],
providers: [{ provide: UserApi, useValue: fakeApi }],
});Jasmine 间谍
jasmine.createSpyObj 会创建一个带有间谍方法的伪造对象,因此您既可以设定返回值,也可以断言调用情况。
const apiSpy = jasmine.createSpyObj<UserApi>('UserApi', ['getUser']);
apiSpy.getUser.and.returnValue(of({ name: 'Ada' }));
TestBed.configureTestingModule({
providers: [{ provide: UserApi, useValue: apiSpy }],
});断言服务已被调用
对组件执行操作后,验证间谍是否收到了预期的参数。
fixture.componentInstance.load(7);
fixture.detectChanges();
expect(apiSpy.getUser).toHaveBeenCalledWith(7);
expect(apiSpy.getUser).toHaveBeenCalledTimes(1);从模拟对象返回 Observable
服务通常会返回 Observable。使用 RxJS 的 of() 模拟同步成功,使用 throwError() 模拟失败,这样就能测试两个分支。
import { of, throwError } from 'rxjs';
apiSpy.getUser.and.returnValue(of({ name: 'Ada' })); // success
apiSpy.getUser.and.returnValue(throwError(() => new Error('boom'))); // error使用 useClass 创建可复用的伪造对象
当一个伪造对象会在许多测试规范中重复使用时,请定义一个类,并通过 useClass 将其连接起来。
class FakeUserApi {
getUser() { return of({ name: 'Test' }); }
}
TestBed.configureTestingModule({
providers: [{ provide: UserApi, useClass: FakeUserApi }],
});使用 HttpTestingController 模拟 HttpClient
要测试调用 HTTP 的服务,而不必伪造整个服务,请导入 provideHttpClientTesting(),并通过 HttpTestingController 刷新请求。
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting()],
});刷新 HTTP 请求
预期会收到请求,使用伪造数据响应,然后验证没有剩余的意外调用。
const http = TestBed.inject(HttpTestingController);
service.load();
const req = http.expectOne('/api/users/7');
expect(req.request.method).toBe('GET');
req.flush({ name: 'Ada' });
http.verify();按测试用例覆盖提供者
有时某个测试需要使用不同的伪造对象。请在 createComponent 之前使用 TestBed.overrideProvider()。
TestBed.overrideProvider(UserApi, {
useValue: { getUser: () => throwError(() => new Error('fail')) },
});
const fixture = TestBed.createComponent(ProfileComponent);只进行足够的模拟
优秀的替身只实现组件实际使用的方法。过度模拟会让测试与内部实现紧密耦合;模拟不足则会使真实的副作用泄漏出来。请匹配组件所依赖的公共契约。
监视真实服务的方法
您也可以保留真实服务,但使用 spyOn(instance, 'method') 监视其中一个方法。当服务的大部分行为都无害、但必须拦截某次调用时,这种方式非常有用。
const svc = TestBed.inject(AnalyticsService);
spyOn(svc, 'track');
fixture.componentInstance.onClick();
expect(svc.track).toHaveBeenCalledWith('click');快速检查
如何让伪造对象既能断言调用,又能控制返回值?
回顾
您使用 useValue、useClass 和 Jasmine spy 将真实依赖替换为替身;使用 of/throwError 返回 Observable;通过 HttpTestingController 测试 HTTP;并按测试用例覆盖提供者。只模拟组件使用的契约。
常见问题解答
「模拟服务与依赖项」课时是免费的吗?
是的 — 「模拟服务与依赖项」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Angular Academy 课程的其余内容,请升级到 CoddyKit PRO。 Angular Academy 课程共包含 4 节课。
「模拟服务与依赖项」这节课中我会学到什么?
通过 DI 提供测试替身 你通过在浏览器中直接运行的动手代码来练习 Angular Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Angular Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Angular Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「模拟服务与依赖项」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Angular Academy 课中编写并运行代码吗?
能。每节 Angular Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。