如何在 Angular 测试中使用间谍

简介

茉莉spies用于跟踪或存根函数或方法。间谍是一种检查函数是否被调用或提供自定义返回值的方法。我们可以使用间谍来测试依赖于服务的组件,从而避免实际调用服务的方法来获取值。这有助于使我们的单元测试专注于测试组件本身的内部,而不是它的依赖项。

在本文中,您将学习如何在一个角度项目中使用茉莉花间谍。

前提条件

要完成本教程,您需要:

本教程已经在Node v16.2.0、npmv7.15.1和@angular/corev12.0.4上进行了验证。

第一步-设置项目

让我们使用一个非常类似于我们在单位角度的tests简介》中使用的例子。

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

1ng new angular-test-spies-example

然后,导航到新创建的项目目录:

1cd angular-test-spies-example

以前,应用程序使用两个按钮在0到15之间递增和递减值。

在本教程中,逻辑将移至服务。这将允许多个组件访问相同的中心值。

1ng generate service increment-decrement

然后在您的代码编辑器中打开increen-ducment.service.ts,并将内容替换为以下代码:

 1[label src/app/increment-decrement.service.ts]
 2import { Injectable } from '@angular/core';
 3
 4@Injectable({
 5  providedIn: 'root'
 6})
 7export class IncrementDecrementService {
 8  value = 0;
 9  message!: string;
10
11  increment() {
12    if (this.value < 15) {
13      this.value += 1;
14      this.message = '';
15    } else {
16      this.message = 'Maximum reached!';
17    }
18  }
19
20  decrement() {
21    if (this.value > 0) {
22      this.value -= 1;
23      this.message = '';
24    } else {
25      this.message = 'Minimum reached!';
26    }
27  }
28}

在代码编辑器中打开app.Component.ts,并将内容替换为以下代码:

 1[label src/app/app.component.ts]
 2import { Component } from '@angular/core';
 3import { IncrementDecrementService } from './increment-decrement.service';
 4
 5@Component({
 6  selector: 'app-root',
 7  templateUrl: './app.component.html',
 8  styleUrls: ['./app.component.css']
 9})
10export class AppComponent {
11  constructor(public incrementDecrement: IncrementDecrementService) { }
12
13  increment() {
14    this.incrementDecrement.increment();
15  }
16
17  decrement() {
18    this.incrementDecrement.decrement();
19  }
20}

在代码编辑器中打开app.Component.html,并将内容替换为以下代码:

 1[label src/app/app.component.html]
 2<h1>{{ incrementDecrement.value }}</h1>
 3
 4<hr>
 5
 6<button (click)="increment()" class="increment">Increment</button>
 7
 8<button (click)="decrement()" class="decrement">Decrement</button>
 9
10<p class="message">
11  {{ incrementDecrement.message }}
12</p>

接下来,在代码编辑器中打开app.Component.spec.ts并修改以下代码行:

 1[label src/app/app.component.spec.ts]
 2import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
 3import { By } from '@angular/platform-browser';
 4import { DebugElement } from '@angular/core';
 5
 6import { AppComponent } from './app.component';
 7import { IncrementDecrementService } from './increment-decrement.service';
 8
 9describe('AppComponent', () => {
10  let fixture: ComponentFixture<AppComponent>;
11  let debugElement: DebugElement;
12  let incrementDecrementService: IncrementDecrementService;
13
14  beforeEach(waitForAsync(() => {
15    TestBed.configureTestingModule({
16      declarations: [
17        AppComponent
18      ],
19      providers: [ IncrementDecrementService ]
20    }).compileComponents();
21
22    fixture = TestBed.createComponent(AppComponent);
23    debugElement = fixture.debugElement;
24
25    incrementDecrementService = debugElement.injector.get(IncrementDecrementService);
26  }));
27
28  it('should increment in template', () => {
29    debugElement
30      .query(By.css('button.increment'))
31      .triggerEventHandler('click', null);
32
33    fixture.detectChanges();
34
35    const value = debugElement.query(By.css('h1')).nativeElement.innerText;
36
37    expect(value).toEqual('1');
38  });
39
40  it('should stop at 15 and show maximum message', () => {
41    incrementDecrementService.value = 15;
42    debugElement
43      .query(By.css('button.increment'))
44      .triggerEventHandler('click', null);
45
46    fixture.detectChanges();
47
48    const value = debugElement.query(By.css('h1')).nativeElement.innerText;
49    const message = debugElement.query(By.css('p.message')).nativeElement.innerText;
50
51    expect(value).toEqual('15');
52    expect(message).toContain('Maximum');
53  });
54});

注意我们如何通过debugElement.injector.get获取对注入服务的引用。

以这种方式测试我们的组件是可行的,但也会对服务进行实际调用,并且我们的组件不是单独测试的。接下来,我们将探索如何使用间谍来检查方法是否已被调用或提供存根返回值。

第二步--刺探服务的方法

下面是如何使用Jasmine的spyOn函数调用服务方法并测试它是否被调用:

 1[label src/app/app.component.spec.ts]
 2import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
 3import { By } from '@angular/platform-browser';
 4import { DebugElement } from '@angular/core';
 5
 6import { AppComponent } from './app.component';
 7import { IncrementDecrementService } from './increment-decrement.service';
 8
 9describe('AppComponent', () => {
10  let fixture: ComponentFixture<AppComponent>;
11  let debugElement: DebugElement;
12  let incrementDecrementService: IncrementDecrementService;
13  let incrementSpy: any;
14
15  beforeEach(waitForAsync(() => {
16    TestBed.configureTestingModule({
17      declarations: [
18        AppComponent
19      ],
20      providers: [ IncrementDecrementService ]
21    }).compileComponents();
22
23    fixture = TestBed.createComponent(AppComponent);
24    debugElement = fixture.debugElement;
25
26    incrementDecrementService = debugElement.injector.get(IncrementDecrementService);
27    incrementSpy = spyOn(incrementDecrementService, 'increment').and.callThrough();
28  }));
29
30  it('should call increment on the service', () => {
31    debugElement
32      .query(By.css('button.increment'))
33      .triggerEventHandler('click', null);
34
35    expect(incrementDecrementService.value).toBe(1);
36    expect(incrementSpy).toHaveBeenCalled();
37  });
38});

spyOn有两个参数:类实例(在本例中是我们的服务实例)和一个字符串值,其中包含要监视的方法或函数的名称。

在这里,我们还在间谍上链接了.and.allThree(),因此实际的方法仍将被调用。在这种情况下,我们的间谍只能用来判断方法是否被实际调用,并监视参数。

下面是一个断言方法被调用了两次的示例:

1expect(incrementSpy).toHaveBeenCalledTimes(2);

下面是一个断言没有使用参数‘Error’调用方法的示例:

1expect(incrementSpy).not.toHaveBeenCalledWith('error');

如果我们想要避免实际调用服务上的方法,我们可以对间谍使用.and.returValue

我们的示例方法不是很好的候选方法,因为它们不返回任何内容,而是改变内部属性。

让我们向我们的服务添加一个实际返回值的新方法:

1[label src/app/increment-decrement.service.ts]
2minimumOrMaximumReached() {
3  return !!(this.message && this.message.length);
4}

<$>[备注] 注意: 在表达式前使用!!会将值强制化为布尔值。 <$>

我们还向组件添加了一个新方法,模板将使用该方法来获得值:

1[label src/app/app.component.ts]
2limitReached() {
3  return this.incrementDecrement.minimumOrMaximumReached();
4}

现在,如果达到以下限制,我们的模板将显示一条消息:

1[label src/app/app.component.html]
2<p class="message" *ngIf="limitReached()">
3  Limit reached!
4</p>

然后,我们可以测试我们的模板消息是否会显示是否达到限制,而不必求助于实际调用服务上的方法:

 1[label src/app/app.component.spec.ts]
 2import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
 3import { By } from '@angular/platform-browser';
 4import { DebugElement } from '@angular/core';
 5
 6import { AppComponent } from './app.component';
 7import { IncrementDecrementService } from './increment-decrement.service';
 8
 9describe('AppComponent', () => {
10  let fixture: ComponentFixture<AppComponent>;
11  let debugElement: DebugElement;
12  let incrementDecrementService: IncrementDecrementService;
13  let minimumOrMaximumSpy: any;
14
15  beforeEach(waitForAsync(() => {
16    TestBed.configureTestingModule({
17      declarations: [
18        AppComponent
19      ],
20      providers: [ IncrementDecrementService ]
21    }).compileComponents();
22
23    fixture = TestBed.createComponent(AppComponent);
24    debugElement = fixture.debugElement;
25
26    incrementDecrementService = debugElement.injector.get(IncrementDecrementService);
27    minimumOrMaximumSpy = spyOn(incrementDecrementService, 'minimumOrMaximumReached').and.returnValue(true);
28  }));
29
30  it(`should show 'Limit reached' message`, () => {
31    fixture.detectChanges();
32
33    const message = debugElement.query(By.css('p.message')).nativeElement.innerText;
34
35    expect(message).toEqual('Limit reached!');
36  });
37});

总结

在本文中,您学习了如何在一个有角度的项目中使用茉莉间谍。

如果您想了解更多有关ANGLE的信息,请查看我们的ANGLE主题页面以获取练习和编程项目。

Published At
Categories with 技术
Tagged with
comments powered by Disqus