簡介
Jasmine spy 用于跟蹤或存根函數或方法。spy 是一種檢查函數是否被調用或提供自定義返回值的方法。我們可以使用spy 來測試依賴于服務的組件,并避免實際調用服務的方法來獲取值。這有助于保持我們的單元測試專注于測試組件本身的內部而不是其依賴關系。
在本文中,您將學習如何在 Angular 項目中使用 Jasmine spy。
先決條件
要完成本教程,您需要:
- 在本地安裝 Node.js,您可以按照《如何安裝 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
并用以下代碼替換內容:
<h1>{{ incrementDecrement.value }}</h1><hr><button (click)="increment()" class="increment">Increment</button><button (click)="decrement()" class="decrement">Decrement</button><p class="message">{{ incrementDecrement.message }}
</p>
接下來,打開您的代碼編輯器中的 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<AppComponent>;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<AppComponent>;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).toHaveBeenCalled();});
});
spyOn
接受兩個參數:類實例(在本例中是我們的服務實例)和一個字符串值,表示要監視的方法或函數的名稱。
在這里,我們還在 spy 上鏈接了 .and.callThrough()
,這樣實際方法仍然會被調用。在這種情況下,我們的 spy 只用于判斷方法是否被調用以及監視參數。
以下是斷言方法被調用兩次的示例:
expect(incrementSpy).toHaveBeenCalledTimes(2);
以下是斷言方法未被使用 'error'
參數調用的示例:
expect(incrementSpy).not.toHaveBeenCalledWith('error');
如果我們想避免實際調用服務上的方法,可以在 spy 上使用 .and.returnValue
。
我們的示例方法不適合這樣做,因為它們不返回任何內容,而是改變內部屬性。
讓我們向服務添加一個實際返回值的新方法:
minimumOrMaximumReached() {return !!(this.message && this.message.length);
}
我們還向組件添加一個新方法,模板將使用它來獲取值:
limitReached() {return this.incrementDecrement.minimumOrMaximumReached();
}
現在,如果達到限制,我們的模板將顯示一條消息:
<p class="message" *ngIf="limitReached()">Limit reached!
</p>
然后,我們可以測試當達到限制時,我們的模板消息是否會顯示,而無需實際調用服務上的方法:
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<AppComponent>;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。
如果您想了解更多關于 Angular 的知識,請查看我們的 Angular 主題頁面,了解練習和編程項目。