当前位置: 首页
前端开发
Angular测试中Spy使用教程详解

Angular测试中Spy使用教程详解

热心网友 时间:2026-06-17
转载

简介

Jasmine spy 是一种用于跟踪或存根函数或方法的技术手段。简单来说,它允许你检查某个函数是否被调用,或者提供一个自定义返回值来替代实际执行。在 Angular 单元测试中,这尤为实用:当你的组件依赖某个服务时,通过 spy 可以避免真实调用服务方法获取数据。这样一来,单元测试就能专注于测试组件自身的逻辑,而不是外部依赖的行为。

在Angular测试中使用spy的教程详解

本教程将带你一步步掌握如何在 Angular 项目中使用 Jasmine spy 进行高效测试。

先决条件

开始前,你的本地环境需具备以下条件:

  • 安装了 Node.js
  • 熟悉搭建 Angular 项目的基本操作

本教程的验证环境为 Node v16.2.0、npm v7.15.1 和 @angular/core v12.0.4。

步骤 1 — 项目设置

我们从与 Angular 单元测试入门教程相似的示例开始。

先用 @angular/cli 创建一个新项目:

ng new angular-test-spies-example

然后进入刚创建的项目文件夹:

cd angular-test-spies-example

之前的示例通过两个按钮在 0 到 15 之间进行增减操作。这一次,我们将逻辑迁移到服务中,以便多个组件共享同一个中心值。

ng generate service increment-decrement

在编辑器中打开 increment-decrement.service.ts 文件,并替换为以下代码:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class IncrementDecrementService {
  value = 0;
  message!: string;

  increment() {
    if (this.value < 15) {
      this.value += 1;
      this.message = '';
    } else {
      this.message = 'Maximum reached!';
    }
  }

  decrement() {
    if (this.value > 0) {
      this.value -= 1;
      this.message = '';
    } else {
      this.message = 'Minimum reached!';
    }
  }
}

接着更新 app.component.ts 的内容:

import { Component } from '@angular/core';
import { IncrementDecrementService } from './increment-decrement.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(public incrementDecrement: IncrementDecrementService) { }

  increment() {
    this.incrementDecrement.increment();
  }

  decrement() {
    this.incrementDecrement.decrement();
  }
}

再替换 app.component.html 的内容:

{{ incrementDecrement.value }}


{{ incrementDecrement.message }}

最后,修改 app.component.spec.ts,同步更新测试代码:

import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './app.component';
import { IncrementDecrementService } from './increment-decrement.service';

describe('AppComponent', () => {
  let fixture: ComponentFixture;
  let debugElement: DebugElement;
  let incrementDecrementService: IncrementDecrementService;

  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [ IncrementDecrementService ]
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    debugElement = fixture.debugElement;

    incrementDecrementService = debugElement.injector.get(IncrementDecrementService);
  }));

  it('should increment in template', () => {
    debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    fixture.detectChanges();

    const value = debugElement.query(By.css('h1')).nativeElement.innerText;

    expect(value).toEqual('1');
  });

  it('should stop at 15 and show maximum message', () => {
    incrementDecrementService.value = 15;
    debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    fixture.detectChanges();

    const value = debugElement.query(By.css('h1')).nativeElement.innerText;
    const message = debugElement.query(By.css('p.message')).nativeElement.innerText;

    expect(value).toEqual('15');
    expect(message).toContain('Maximum');
  });
});

这里有一个小技巧:通过 debugElement.injector.get 可以获取注入的服务实例引用。

虽然这种方式可行,但每次操作都会真实调用服务方法,导致组件并未完全隔离测试。接下来,我们将学习如何使用 spy 来检查方法是否被调用,或者提供存根返回值。

步骤 2 —— 监视服务方法

下面的例子展示了如何使用 Jasmine 的 spyOn 函数来监视一个服务方法,并测试它是否被真正调用:

import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './app.component';
import { IncrementDecrementService } from './increment-decrement.service';

describe('AppComponent', () => {
  let fixture: ComponentFixture;
  let debugElement: DebugElement;
  let incrementDecrementService: IncrementDecrementService;
  let incrementSpy: any;

  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [ IncrementDecrementService ]
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    debugElement = fixture.debugElement;

    incrementDecrementService = debugElement.injector.get(IncrementDecrementService);
    incrementSpy = spyOn(incrementDecrementService, 'increment').and.callThrough();
  }));

  it('should call increment on the service', () => {
    debugElement
      .query(By.css('button.increment'))
      .triggerEventHandler('click', null);

    expect(incrementDecrementService.value).toBe(1);
    expect(incrementSpy).toHa veBeenCalled();
  });
});

spyOn 接收两个参数:第一个是类的实例(此处为服务实例),第二个是字符串,表示要监视的方法名称。

这里我们还链式调用了 .and.callThrough(),目的是让实际方法继续执行。这种情况下,spy 只负责判断方法是否被调用,并记录传入的参数。

如果要断言方法被调用了两次,可以这样写:

expect(incrementSpy).toHa veBeenCalledTimes(2);

如果想断言方法没有被传入 'error' 参数调用,则这样写:

expect(incrementSpy).not.toHa veBeenCalledWith('error');

如果我们希望避免真正执行服务里的方法,可以使用 .and.returnValue。不过刚才的例子中的方法并不适合这么做,因为它们不返回任何值,仅修改内部属性。

我们来给服务添加一个真正有返回值的新方法:

minimumOrMaximumReached() {
  return !!(this.message && this.message.length);
}

同时给组件也添加一个方法,供模板调用:

limitReached() {
  return this.incrementDecrement.minimumOrMaximumReached();
}

这样一来,我们就可以测试当达到限制时模板消息是否正确显示,而完全不需要真的去执行服务中的方法:

import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';

import { AppComponent } from './app.component';
import { IncrementDecrementService } from './increment-decrement.service';

describe('AppComponent', () => {
  let fixture: ComponentFixture;
  let debugElement: DebugElement;
  let incrementDecrementService: IncrementDecrementService;
  let minimumOrMaximumSpy: any;

  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [
        AppComponent
      ],
      providers: [ IncrementDecrementService ]
    }).compileComponents();

    fixture = TestBed.createComponent(AppComponent);
    debugElement = fixture.debugElement;

    incrementDecrementService = debugElement.injector.get(IncrementDecrementService);
    minimumOrMaximumSpy = spyOn(incrementDecrementService, 'minimumOrMaximumReached').and.returnValue(true);
  }));

  it(`should show 'Limit reached' message`, () => {
    fixture.detectChanges();

    const message = debugElement.query(By.css('p.message')).nativeElement.innerText;

    expect(message).toEqual('Limit reached!');
  });
});

结论

希望通过本教程,你已经掌握了在 Angular 项目中使用 Jasmine spy 的实战技巧。

来源:https://www.jb51.net/javascript/318813z8n.htm

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
深入理解调用栈与异步任务的状态同步机制

深入理解调用栈与异步任务的状态同步机制

调用栈仅记录同步执行流,异步回调由事件循环调度、任务队列存放,闭包维持作用域。执行时新建上下文入栈,不复用旧栈帧。调用栈不主动同步异步状态,事件循环是真正的协调者。

时间:2026-07-08 06:57
HTML自定义元素实现跨组件通信的完整指南

HTML自定义元素实现跨组件通信的完整指南

自定义元素跨组件通信需依赖CustomEvent,且bubbles和composed必须同时设为true才能穿透ShadowDOM。事件监听应挂载在document或父容器等合适节点,避免使用DOM查找或data ARIA属性替代事件通信,从而确保事件正确穿越边界,保持跨组件通信的可靠性。

时间:2026-07-08 06:56
JavaScript static关键字在工具库构建中的应用

JavaScript static关键字在工具库构建中的应用

在JavaScript工具库构建中,static关键字将函数或常量挂载到类名上,无需实例化即可调用,适用于无状态操作如日期格式化、字符串截断、深克隆,并统一管理配置常量与实现工厂方法,但需避免依赖实例状态。

时间:2026-07-08 06:56
如何避免静态初始化块异常导致应用冷启动中断

如何避免静态初始化块异常导致应用冷启动中断

防范静态初始化失败导致应用中断,需用try-catch兜住异常并提供安全默认值;将高风险逻辑移出static块,改用延迟加载或Holder模式;显式控制初始化顺序并增强可观测性;诊断时直击ExceptionInInitializerError的根因。

时间:2026-07-08 06:56
虚拟DOM标签级diff比对优化过程详解

虚拟DOM标签级diff比对优化过程详解

虚拟DOMdiff以节点为单位,按层级同标签优先判断复用,通过key精准识别节点身份,配合细粒度属性更新及短路优化,避免全量递归比较,只更新变化的节点,跳过冗余计算,将时间复杂度从O(n³)降至接近O(n),从而大幅提升渲染性能。

时间:2026-07-08 06:56
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜