각도 테스트에서 스파이를 사용하는 방법
소개
Jasmine 스파이는 함수 또는 메서드를 추적하거나 스텁하는 데 사용됩니다. 스파이는 함수가 호출되었는지 확인하거나 사용자 지정 반환 값을 제공하는 방법입니다. 스파이를 사용하여 서비스에 의존하는 구성 요소를 테스트하고 실제로 값을 얻기 위해 서비스의 메서드를 호출하는 것을 피할 수 있습니다. 이렇게 하면 종속성 대신 구성 요소 자체의 내부 테스트에 단위 테스트를 집중할 수 있습니다.
이 기사에서는 Angular 프로젝트에서 Jasmine 스파이를 사용하는 방법을 배웁니다.
전제 조건
이 자습서를 완료하려면 다음이 필요합니다.
- 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
을 사용하여 삽입된 서비스에 대한 참조를 얻는 방법에 주목하십시오.
이 방법으로 구성 요소를 테스트하는 것은 가능하지만 서비스에 대한 실제 호출도 이루어지며 구성 요소는 별도로 테스트되지 않습니다. 다음으로 스파이를 사용하여 메서드가 호출되었는지 확인하거나 스텁 반환 값을 제공하는 방법을 살펴보겠습니다.
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
은 클래스 인스턴스(이 경우 서비스 인스턴스)와 스파이할 메서드 또는 함수의 이름이 포함된 문자열 값의 두 가지 인수를 사용합니다.
여기서 우리는 또한 스파이에 .and.callThrough()
를 연결했습니다. 그래서 실제 메서드는 여전히 호출될 것입니다. 이 경우 스파이는 메서드가 실제로 호출되었는지 확인하고 인수를 감시하는 데에만 사용됩니다.
다음은 메서드가 두 번 호출되었다고 어설션하는 예입니다.
expect(incrementSpy).toHaveBeenCalledTimes(2);
다음은 인수 error
를 사용하여 메서드가 호출되지 않았다고 주장하는 예입니다.
expect(incrementSpy).not.toHaveBeenCalledWith('error');
서비스에서 메소드를 실제로 호출하지 않으려면 스파이에서 .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 스파이를 사용하는 방법을 배웠습니다.
Angular에 대해 자세히 알아보려면 연습 및 프로그래밍 프로젝트에 대한 Angular 주제 페이지를 확인하세요.