Angular2에서 EventEmitter를 테스트하는 방법이 있습니까?


87

EventEmitter를 사용하는 구성 요소가 있고 EventEmitter는 페이지의 누군가를 클릭 할 때 사용됩니다. 단위 테스트 중에 EventEmitter를 관찰하고 TestComponentBuilder를 사용하여 EventEmitter.next () 메서드를 트리거하는 요소를 클릭하고 전송 된 내용을 볼 수있는 방법이 있습니까?


시도한 것을 보여주는 플 런커를 제공 할 수 있습니까? 그러면 누락 된 부분을 추가 할 수 있습니다.
Günter Zöchbauer

답변:


204

테스트는 다음과 같습니다.

it('should emit on click', () => {
   const fixture = TestBed.createComponent(MyComponent);
   // spy on event emitter
   const component = fixture.componentInstance; 
   spyOn(component.myEventEmitter, 'emit');

   // trigger the click
   const nativeElement = fixture.nativeElement;
   const button = nativeElement.querySelector('button');
   button.dispatchEvent(new Event('click'));

   fixture.detectChanges();

   expect(component.myEventEmitter.emit).toHaveBeenCalledWith('hello');
});

구성 요소가 다음과 같은 경우

@Component({ ... })
class MyComponent {
  @Output myEventEmitter = new EventEmitter<string>();

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}

1
버튼 대신 클릭하는 앵커 인 경우 쿼리 선택기가 버튼 대신에 표시됩니까? 나는 그 구성 요소와 똑같은 것을 사용하고 있지만 'expect (value) .toBe ('hello ');' 절대 실행되지 않습니다. 대신 닻이기 때문인지 궁금합니다.
tallkid24 2016-02-10

나는 실제 이미 터 대신 스파이를 사용하여 테스트하는 더 깨끗한 방법으로 내 대답을 업데이트했으며 작동해야한다고 생각합니다 (실제로 ebook의 샘플에 대해 수행하는 작업입니다).
cexbrayat

감사합니다! 저는 프런트 엔드 개발, 특히 유닛 테스트를 처음 사용합니다. 이것은 많은 도움이됩니다. 스파이 온 기능이 존재하는지조차 몰랐습니다.
tallkid24 2016-02-10

TestComponent를 사용하여 MyComponent를 래핑하는 경우 어떻게 테스트 할 수 있습니까? 예를 들어 html = <my-component (myEventEmitter)="function($event)"></my-component>및 테스트에서 다음을 수행합니다. tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos

1
멋진 대답 - 매우 간결하고 요점 - 매우 유용한 일반적인 패턴
danday74

48

스타일에 따라 스파이를 사용할 수 있습니다. 스파이를 사용하여 쉽게 emit해고 되는지 확인하는 방법은 다음과 같습니다 .

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});

이전 의견에서 지적한 것처럼 문제가 될 수있는 async 또는 fakeAsync를 불필요하게 사용하지 않도록 답변을 업데이트했습니다. 이 답변은 Angular 9.1.7부터 좋은 솔루션입니다. 변경 사항이 있으면 의견을 남겨 주시면이 답변을 업데이트하겠습니다. 댓글 / 조정 해 주신 모든 분들께 감사드립니다.
Joshua Michael Wagoner

당신 expect은 실제 스파이 ( spyOn()전화 결과)가 되어야하지 않습니까?
Yuri

스파이 온 후 "component.buttonClick ()"을 놓쳤습니다. 이 솔루션으로 문제가 해결되었습니다. 감사합니다!
Pearl

2

에미 터를 구독하거나 @Output()부모 템플릿에서 이미 터에 바인딩 할 수 있으며 바인딩이 업데이트 된 경우 부모 구성 요소를 체크인 할 수 있습니다. 클릭 이벤트를 전달하면 구독이 시작됩니다.


그래서 만약 내가 emitter.subscribe (data => {}); next () 출력은 어떻게 얻습니까?
tallkid24 2016-02-10

바로 그거죠. 또는에서 템플릿이 TestComponent있다 <my-component (someEmitter)="value=$event">(여기서 someEmitter입니다 @Output()후) value의 특성 TextComponent발송 된 이벤트와 업데이트해야합니다.
Günter Zöchbauer

0

방출 된 배열의 길이를 테스트해야했습니다. 그래서 이것이 다른 Answers 위에 이것을 한 방법입니다.

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);

-2

가장 많이 득표 한 답변이 작동하지만 좋은 테스트 관행을 보여주지 않으므로 몇 가지 실용적인 예를 통해 Günter의 답변 을 확장 할 것이라고 생각했습니다 .

다음과 같은 간단한 구성 요소가 있다고 가정 해 보겠습니다.

@Component({
  selector: 'my-demo',
  template: `
    <button (click)="buttonClicked()">Click Me!</button>
  `
})
export class DemoComponent {
  @Output() clicked = new EventEmitter<string>();

  constructor() { }

  buttonClicked(): void {
    this.clicked.emit('clicked!');
  }
}

구성 요소는 테스트중인 시스템이며 일부를 감시하면 캡슐화가 중단됩니다. 각도 구성 요소 테스트는 다음 세 가지만 알아야합니다.

  • DOM (예 :를 통해 액세스 fixture.nativeElement.querySelector)
  • @Inputs 및 @Outputs 의 이름 ; 과
  • 협업 서비스 (DI 시스템을 통해 주입 됨).

인스턴스에서 직접 메서드를 호출하거나 구성 요소의 일부를 감시하는 것과 관련된 모든 것은 구현과 너무 밀접하게 연결되어 리팩토링에 마찰을 추가합니다. 테스트 이중은 공동 작업자에게만 사용해야합니다. 이 경우에는 공동 작업자가 없으므로 모의, 스파이 또는 기타 테스트 복식이 필요 하지 않습니다 .


이를 테스트하는 한 가지 방법은 이미 터를 직접 구독 한 다음 클릭 작업을 호출하는 것 입니다 (입력 및 출력이있는 구성 요소 참조 ).

describe('DemoComponent', () => {
  let component: DemoComponent;
  let fixture: ComponentFixture<DemoComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ DemoComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(DemoComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should emit when clicked', () => {
    let emitted: string;
    component.clicked.subscribe((event: string) => {
      emitted = event;
    });

    fixture.nativeElement.querySelector('button').click();

    expect(emitted).toBe('clicked!');
  });
});

이는 구성 요소 인스턴스와 직접 상호 작용하지만의 이름은 @Output공용 API의 일부이므로 너무 밀접하게 결합되어 있지 않습니다.


또는 간단한 테스트 호스트를 만들고 (테스트 호스트 내부의 구성 요소 참조 ) 실제로 구성 요소를 마운트 할 수 있습니다.

@Component({
  selector: 'test-host',
  template: `
    <my-demo (clicked)="onClicked($event)"></my-demo>
  `
})
class TestHostComponent {
  lastClick = '';

  onClicked(value: string): void {
    this.lastClick = value;
  }
}

그런 다음 컨텍스트에서 구성 요소를 테스트합니다.

describe('DemoComponent', () => {
  let component: TestHostComponent;
  let fixture: ComponentFixture<TestHostComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ TestHostComponent, DemoComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should emit when clicked', () => {
    fixture.nativeElement.querySelector('button').click();

    expect(component.lastClick).toBe('clicked!');
  });
});

componentInstance여기입니다 테스트 호스트 우리가 너무 우리가 실제로 테스트하고 구성 요소에 연결하지 않을 확신 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.