어트리뷰트 디렉티브 는 엘리먼트, 컴포넌트, 다른 디렉티브의 동작을 변경합니다. 호스트 엘리먼트의 어트리뷰트로 적용하기 때문에 이렇게 부릅니다.
Highlight 디렉티브 테스트하기
예제 애플리케이션의 Highlight 디렉티브는 데이터 바인딩된 색상이나 기본 색상(lightgray)을 엘리먼트의 배경색으로 지정합니다.
엘리먼트의 커스텀 프로퍼티(customProperty)도 true로 설정하는데, 이는 이런 동작도 가능하다는 것을 보여주기 위한 것입니다.
import {Directive, inject, input} from '@angular/core';
/**
* 적용된 엘리먼트의 backgroundColor를 강조 색상으로 지정하고
* 엘리먼트의 customProperty 어트리뷰트를 true로 설정합니다
*/
@Directive({
selector: '[highlight]',
host: {
'[style.backgroundColor]': 'bgColor() || defaultColor',
},
})
export class Highlight {
readonly defaultColor = 'rgb(211, 211, 211)'; // 밝은 회색
readonly bgColor = input('', {alias: 'highlight'});
}
이 디렉티브는 애플리케이션 곳곳에서 사용됩니다.
가장 간단한 예는 About 컴포넌트입니다.
@Component({
imports: [Twain, Highlight],
template: `
<h2 highlight="skyblue">About</h2>
<h3>Quote of the day:</h3>
<twain-quote />
`,
})
export class About {}
About 컴포넌트 안에서 Highlight 디렉티브가 사용되는 방식을 테스트하려면 컴포넌트 테스트 시나리오의 중첩 컴포넌트 테스트에서 알아본 방법만으로 충분합니다.
let fixture: ComponentFixture<About>;
beforeEach(async () => {
TestBed.configureTestingModule({
providers: [TwainService, UserService],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
});
fixture = TestBed.createComponent(About);
await fixture.whenStable();
});
it('should have skyblue <h2>', () => {
const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
const bgColor = h2.style.backgroundColor;
expect(bgColor).toBe('skyblue');
});
하지만 사용 사례 하나만 테스트해서는 디렉티브의 기능 전체를 확인하기 어렵습니다. 디렉티브를 사용하는 모든 컴포넌트를 찾아 테스트하는 일은 번거롭고 변경에 취약하며, 이 방법 역시 모든 기능을 검증하기 어렵습니다.
클래스만 테스트 하는 것도 도움이 될 수 있지만, 이런 어트리뷰트 디렉티브는 주로 DOM을 조작합니다. 독립적인 단위 테스트는 DOM을 다루지 않으므로 디렉티브가 제대로 동작한다고 확신하기 어렵습니다.
디렉티브를 적용하는 모든 방식을 보여주는 테스트 전용 컴포넌트를 만드는 것이 더 좋습니다.
@Component({
imports: [Highlight],
template: `
<h2 highlight="yellow">Something Yellow</h2>
<h2 highlight>The Default (Gray)</h2>
<h2>No Highlight</h2>
<input #box [highlight]="box.value" value="cyan" />
`,
})
class Test {}
참고: <input> 예제는 입력 필드에 있는 색상 이름을 Highlight에 바인딩합니다.
초기값은 "cyan"이므로 입력 필드의 배경색도 이 색이어야 합니다.
이 컴포넌트의 테스트는 다음과 같습니다.
let fixture: ComponentFixture<Test>;
let des: DebugElement[]; // 디렉티브가 적용된 엘리먼트 세 개
beforeEach(async () => {
fixture = TestBed.createComponent(Test);
await fixture.whenStable();
// Highlight가 적용된 모든 엘리먼트
des = fixture.debugElement.queryAll(By.directive(Highlight));
});
// 색상 테스트
it('should have three highlighted elements', () => {
expect(des.length).toBe(3);
});
it('should color 1st <h2> background "yellow"', () => {
const bgColor = des[0].nativeElement.style.backgroundColor;
expect(bgColor).toBe('yellow');
});
it('should color 2nd <h2> background w/ default color', () => {
const dir = des[1].injector.get(Highlight);
const bgColor = des[1].nativeElement.style.backgroundColor;
expect(bgColor).toBe(dir.defaultColor);
});
it('should bind <input> background to value color', async () => {
// nativeElement를 사용하면 더 간단합니다
const input = des[2].nativeElement as HTMLInputElement;
expect(input.style.backgroundColor, 'initial backgroundColor').toBe('cyan');
input.value = 'green';
// Angular가 입력값 변경에 반응하도록 DOM 이벤트를 발생시킵니다.
input.dispatchEvent(new Event('input'));
await fixture.whenStable();
expect(input.style.backgroundColor, 'changed backgroundColor').toBe('green');
});
it('bare <h2> should not have a backgroundColor', () => {
// Highlight 디렉티브가 없는 h2
const bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
expect(bareH2.styles.backgroundColor).toBeUndefined();
});
몇 가지 방법을 눈여겨보세요.
By.directive조건식은 엘리먼트 타입을 모를 때 해당 디렉티브가 적용된 엘리먼트를 찾기에 좋습니다.By.css('h2:not([highlight])')의:not가상 클래스는 디렉티브가 적용되지 않은<h2>엘리먼트를 찾습니다.By.css('*:not([highlight])')는 이 디렉티브가 적용되지 않은 모든 엘리먼트를 찾습니다.DebugElement가 제공하는 추상화 덕분에 실제 브라우저가 없어도DebugElement.styles로 엘리먼트 스타일에 접근할 수 있습니다. 다만nativeElement를 사용하는 것이 더 간단하거나 명확하다면 이를 사용해도 됩니다.Angular는 디렉티브가 적용된 엘리먼트의 인젝터에 디렉티브를 추가합니다. 기본 색상 테스트에서는 두 번째
<h2>의 인젝터로Highlight인스턴스와defaultColor를 가져옵니다.DebugElement.properties로 디렉티브가 설정한 테스트용 커스텀 프로퍼티에 접근할 수 있습니다.
디렉티브를 독립적으로 테스트하기
TestBed.createDirective()로 디렉티브를 독립적으로 생성하면 DirectiveFixture를 반환합니다. 컴포넌트 템플릿을 통해 디렉티브를 렌더링할 수도 있습니다.
다음은 로컬 테스트 컴포넌트의 입력값으로 Highlight 디렉티브를 제어하는 테스트입니다.
@Component({
imports: [Highlight],
template: `<p [highlight]="color()">{{ color() }}</p>`,
})
class Test {
readonly color = input('');
}
describe('Highlight', () => {
let fixture: ComponentFixture<Test>;
beforeEach(async () => {
fixture = TestBed.createComponent(Test);
await fixture.whenStable();
});
it('should use the specified color once an input is provided', async () => {
fixture.componentRef.setInput('color', 'blue');
await fixture.whenStable();
const p = fixture.nativeElement.querySelector('p');
expect(p.style.backgroundColor).toBe('blue');
});
});