如何使用Jest结合Angular注入令牌(Inject Token)测试依赖注入?当前直接访问私有依赖的测试写法是否为最佳实践?
规范测试依赖注入服务的正确姿势
问题背景
我编写了如下测试代码来验证HttpClientService:
describe('HttpClientService', () => { beforeEach(async () => { TestBed.configureTestingModule({ imports: [], declarations: [], providers: [ HttpClientService, { provide: HTTP_CLIENT, useClass: MockHttpClient } ] }).compileComponents() service = TestBed.inject(HttpClientService) }) test('Should create service', () => { expect(service).toBeTruthy() }) test('Should request post with correct url', fakeAsync(() => { service['_http'].post = jest.fn().mockReturnValue(of()) const url = faker.internet.url() const body = faker.random.objectElement() service.post(url, body) tick() expect(service['_http'].post).toHaveBeenCalledWith(url, body) })) })
目前我通过直接引用私有依赖service['_http'].post = jest.fn().mockReturnValue(of())的方式进行测试,想了解是否可以通过TestBed.inject实现更规范的依赖注入测试,或者当前这种直接访问私有依赖的写法是否属于最佳实践?
解答
首先明确:直接访问私有属性的写法绝对不是最佳实践,而通过TestBed.inject获取依赖实例才是更规范、更健壮的测试方式,原因和具体实现如下:
1. 直接访问私有属性的问题
虽然这种写法能暂时通过测试,但它存在致命缺陷:
- 你正在测试类的内部实现细节而非对外暴露的行为,一旦
HttpClientService的内部私有属性命名变更(比如把_http改成_client),测试会直接失败,完全违背了“测试行为而非实现”的核心原则。 - 这种写法破坏了封装性,私有属性本就不应该被外部代码(包括测试)直接访问,不符合面向对象的设计思想。
2. 利用TestBed.inject的规范实现
你已经在测试模块的providers中配置了{ provide: HTTP_CLIENT, useClass: MockHttpClient },这意味着可以直接通过TestBed.inject(HTTP_CLIENT)获取到这个Mock实例,完全不需要碰私有属性。
修改后的测试代码如下:
describe('HttpClientService', () => { let service: HttpClientService; // 直接声明mockHttpClient变量,通过TestBed注入获取 let mockHttpClient: MockHttpClient; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [], declarations: [], providers: [ HttpClientService, { provide: HTTP_CLIENT, useClass: MockHttpClient } ] }).compileComponents(); service = TestBed.inject(HttpClientService); // 这里直接注入MockHttpClient实例 mockHttpClient = TestBed.inject(HTTP_CLIENT) as MockHttpClient; }) test('Should create service', () => { expect(service).toBeTruthy(); }) test('Should request post with correct url', fakeAsync(() => { // 直接对mock实例的post方法进行mock jest.spyOn(mockHttpClient, 'post').mockReturnValue(of()); const url = faker.internet.url(); const body = faker.random.objectElement(); service.post(url, body); tick(); // 验证mock方法的调用 expect(mockHttpClient.post).toHaveBeenCalledWith(url, body); })) })
3. 这种写法的优势
- 解耦测试与实现细节:测试只关注
HttpClientService的对外行为(调用post方法后是否正确转发请求),不管内部怎么存储依赖,测试都不会失效。 - 符合依赖注入设计初衷:依赖注入的核心就是解耦,测试时通过注入获取依赖实例,完美契合这一设计思想。
- 更高的可维护性:后续修改
HttpClientService的内部逻辑时,只要对外行为不变,测试就无需调整。
内容的提问来源于stack exchange,提问作者waldir neto
相关产品推荐
相关产品推荐

