angular中的测试

在 Angular 中,测试是一等公民。Angular 官方提供了完整的测试工具链,让你能够对组件、服务、管道、指令等进行单元测试和集成测试。


📦 核心测试工具

Angular 项目默认使用以下测试工具:

工具 作用
Jasmine 行为驱动开发(BDD)测试框架,提供 describeitexpect 等语法
Karma 测试运行器,可以在真实浏览器中运行测试,并生成覆盖率报告
Angular TestBed Angular 提供的测试工具,用于创建测试模块、注入服务、模拟依赖
Protractor Angular 应用的端到端(E2E)测试框架(Angular 15+ 已弃用,推荐 Cypress 或 Playwright)

🗂️ 测试文件位置和命名规范

Angular CLI 生成的文件会自动附带测试文件,命名规范为 .spec.ts

text

复制代码
src/
├── app/
│   ├── app.component.ts
│   ├── app.component.spec.ts    ← 组件测试
│   ├── services/
│   │   ├── user.service.ts
│   │   └── user.service.spec.ts ← 服务测试
│   └── pipes/
│       ├── filter.pipe.ts
│       └── filter.pipe.spec.ts  ← 管道测试

🧪 运行测试的命令

bash

复制代码
# 运行所有单元测试(监听文件变化,自动重跑)
ng test

# 运行一次并生成覆盖率报告
ng test --code-coverage

# 运行指定文件的测试(需要配合 --include)
ng test --include='**/app.component.spec.ts'

# 端到端测试(Angular 15+ 需要额外配置)
ng e2e

测试运行后会打开浏览器窗口(默认 Chrome),并在终端显示结果。


📝 编写测试的基本语法

1. 基础 Jasmine 语法

typescript

复制代码
describe('测试套件名称', () => {
  // 每个测试用例执行前运行
  beforeEach(() => {
    // 初始化操作
  });

  // 单个测试用例
  it('应该返回 true', () => {
    const result = true;
    expect(result).toBe(true);  // 断言
  });
});

2. 常用断言(Matchers)

typescript

复制代码
expect(value).toBe(expected);              // 严格相等 (===)
expect(value).toEqual(expected);           // 深度相等(对象内容)
expect(value).toBeTruthy();                // 真值
expect(value).toBeFalsy();                 // 假值
expect(value).toBeNull();                  // null
expect(value).toBeUndefined();             // undefined
expect(value).toContain('子字符串');        // 数组或字符串包含
expect(array).toHaveSize(3);               // 数组长度
expect(fn).toHaveBeenCalled();             // 函数被调用
expect(fn).toHaveBeenCalledWith(1, 2);     // 函数被调用的参数

🧩 组件测试(Component Testing)

基本组件测试示例

typescript

复制代码
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  // 每个测试前准备
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [AppComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();  // 触发变更检测
  });

  it('应该创建组件', () => {
    expect(component).toBeTruthy();
  });

  it('应该显示标题', () => {
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('h1')?.textContent).toContain('Hello');
  });
});

模拟用户交互

typescript

复制代码
import { By } from '@angular/platform-browser';

it('点击按钮应该更新内容', () => {
  const button = fixture.debugElement.query(By.css('button')).nativeElement;
  button.click();
  fixture.detectChanges();  // 更新视图
  
  const content = fixture.debugElement.query(By.css('.content')).nativeElement;
  expect(content.textContent).toContain('已点击');
});

🔧 服务测试(Service Testing)

使用 TestBed 注入服务

typescript

复制代码
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('UserService', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],  // 替换真实 HttpClient
      providers: [UserService]
    });
    service = TestBed.inject(UserService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify();  // 确保没有未处理的 HTTP 请求
  });

  it('应该获取用户列表', () => {
    const mockUsers = [{ id: 1, name: '张三' }];

    service.getUsers().subscribe(users => {
      expect(users).toEqual(mockUsers);
    });

    // 模拟 HTTP 请求返回数据
    const req = httpMock.expectOne('https://api.example.com/users');
    expect(req.request.method).toBe('GET');
    req.flush(mockUsers);  // 返回模拟数据
  });
});

🎭 模拟依赖(Mocking)

使用 spy 模拟方法

typescript

复制代码
it('应该调用 UserService 的 getUsers 方法', () => {
  const userService = TestBed.inject(UserService);
  spyOn(userService, 'getUsers').and.returnValue(of([{ id: 1, name: 'Mock User' }]));

  // 执行组件逻辑
  component.loadUsers();
  
  expect(userService.getUsers).toHaveBeenCalled();
  expect(component.users).toEqual([{ id: 1, name: 'Mock User' }]);
});

使用 jasmine.createSpyObj 模拟完整服务

typescript

复制代码
const mockService = jasmine.createSpyObj('UserService', ['getUsers', 'saveUser']);
mockService.getUsers.and.returnValue(of([]));

TestBed.configureTestingModule({
  providers: [
    { provide: UserService, useValue: mockService }
  ]
});

🧪 测试覆盖率

查看覆盖率报告

bash

复制代码
ng test --code-coverage

运行后会在 /coverage 目录生成 HTML 报告,打开 index.html 即可查看:

指标 说明
Statements 语句覆盖率
Branches 分支覆盖率(if/else、switch等)
Functions 函数覆盖率
Lines 行覆盖率

企业项目通常要求核心代码覆盖率达到 80% 以上


📊 测试最佳实践

  1. 每个组件/服务都应有对应的 .spec.ts 文件

  2. 使用 beforeEach 统一准备测试环境,避免重复代码

  3. 测试要独立 :每个 it 块不应依赖其他测试的执行结果

  4. 模拟外部依赖:HTTP 请求、路由、第三方库都应被 Mock

  5. 测试行为而非实现:测试"做什么",而不是"怎么做"

  6. 保持测试快速:避免在单元测试中发起真实 HTTP 请求

  7. 定期运行测试:结合 CI/CD,每次提交代码自动运行测试


🚀 高级测试技术

测试异步代码

typescript

复制代码
import { fakeAsync, tick } from '@angular/core/testing';

it('应该处理异步操作', fakeAsync(() => {
  component.loadData();
  tick(1000);  // 模拟等待 1000ms
  expect(component.data).toBeDefined();
}));

测试路由跳转

typescript

复制代码
import { Router } from '@angular/router';
import { Location } from '@angular/common';

it('应该跳转到详情页', fakeAsync(() => {
  const router = TestBed.inject(Router);
  const location = TestBed.inject(Location);
  
  router.navigate(['/detail', 1]);
  tick();
  expect(location.path()).toBe('/detail/1');
}));

测试输入/输出(@Input / @Output)

typescript

复制代码
@Component({
  template: `<app-child [name]="'Test'" (emitEvent)="onEvent($event)"></app-child>`
})
class TestHostComponent {
  onEvent(data: any) {}
}

it('应该接收输入并触发输出', () => {
  const hostFixture = TestBed.createComponent(TestHostComponent);
  const childComponent = hostFixture.debugElement.query(By.directive(ChildComponent)).componentInstance;
  
  expect(childComponent.name).toBe('Test');
  
  spyOn(hostFixture.componentInstance, 'onEvent');
  childComponent.emitEvent.emit({ data: 'Hello' });
  expect(hostFixture.componentInstance.onEvent).toHaveBeenCalledWith({ data: 'Hello' });
});

📖 总结

测试类型 工具 重点
单元测试 Jasmine + Karma 测试独立的函数、服务、管道、组件
集成测试 TestBed 测试组件之间的交互、路由、依赖注入
端到端测试 Cypress / Playwright 模拟真实用户操作,测试完整流程

Angular 的测试生态非常完善,官方建议在开发过程中持续编写和维护测试,这能显著提高代码质量和项目可维护性。🚀

相关推荐
FL16238631293 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端
用户059540174463 小时前
把 AI 长期记忆去重测试从 20 分钟压到 40 秒,重复率从 18% 干到 1.5%
前端·css
kyriewen4 小时前
我把 AI 写的并发请求控制器手写了一遍——3 个语义我当时根本讲不清
前端·javascript·面试
_codemonster4 小时前
Vue中的ref和reactive到底在干嘛
前端·javascript·vue.js
IT_陈寒4 小时前
为什么我的JavaScript闭包总是漏掉那个变量?
前端·人工智能·后端
两只羊ovo5 小时前
前端八股之 storage & this:两个最容易卡住的点,一次讲透
前端
用户921080262865 小时前
Bubble 打字机效果实践:从项目配置到源码实现
前端
weixin_461408585 小时前
npm npx yarn
开发语言·前端·javascript
四千岁5 小时前
RAG系统中的分块
前端·javascript·后端