Angular中的单元测试点击事件


80

我正在尝试将单元测试添加到Angular 2应用程序中。在我的一个组件中,有一个带有(click)处理程序的按钮。当用户单击按钮时,将调用一个在.ts类文件中定义的函数。该函数在console.log窗口中打印一条消息,表明已按下该按钮。我当前的测试代码对console.log消息的打印进行了测试:

describe('Component: ComponentToBeTested', () => {
    var component: ComponentToBeTested;

    beforeEach(() => {
        component = new ComponentToBeTested();
        spyOn(console, 'log');
    });

    it('should call onEditButtonClick() and print console.log', () => {
        component.onEditButtonClick();
        expect(console.log).toHaveBeenCalledWith('Edit button has been clicked!);
    });
});

但是,这仅测试控制器类,而不测试HTML。我不仅要测试onEditButtonClick调用时是否发生日志记录,还需要进行测试。我还想测试onEditButtonClick用户单击组件的HTML文件中定义的编辑按钮时调用的方法。我怎样才能做到这一点?

Answers:


116

我的目标是检查用户单击编辑按钮时是否调用了“ onEditButtonClick”,而不仅仅是检查正在打印的console.log。

您首先需要使用Angular设置测试TestBed。这样,您实际上可以抓住按钮并单击它。您将要做的是配置模块,就像@NgModule在测试环境中一样,

import { TestBed, async, ComponentFixture } from '@angular/core/testing';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ ],
      declarations: [ TestComponent ],
      providers: [  ]
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;
    });
  }));
});

然后,您需要监视该onEditButtonClick方法,单击按钮,并检查该方法是否已被调用

it('should', async(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();

  fixture.whenStable().then(() => {
    expect(component.onEditButtonClick).toHaveBeenCalled();
  });
}));

这里,我们需要运行async测试,因为单击按钮包含异步事件处理,并且需要通过调用来等待事件处理fixture.whenStable()

也可以看看:

更新

现在,首选使用fakeAsync/tick组合而不是async/whenStable组合。如果进行了XHR调用,则应使用后者,因为fakeAsync它不支持它。因此,除了重构上面的代码外,它看起来像

it('should', fakeAsync(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();
  tick();
  expect(component.onEditButtonClick).toHaveBeenCalled();

}));

不要忘记导入fakeAsynctick


2
如果我们在html文件中有多个按钮怎么办?做类似的事情:Fixture.debugElement.nativeElement.querySelector('button');
爱国

2
在html文件中查找“按钮”,但是我们有两个以上的按钮吗?我应该如何参考第二个按钮出现来进行测试?
爱国

3
我找到了解决方案!button = Fixture.debugElement.queryAll(By.css('button'); button1 = button [0];
Aiguo

3
我只是花了一天的时间来弄清楚为什么这对我不起作用,只是意识到我在调用组件元素的click而不是我实际上在监听单击的div。
majinnaibu

1
button.triggerEventHandler('click',null)和button.click90有什么区别?
jitenagarwal18年

48

可以使用所提供的async/fakeAsync函数测试'@angular/core/testing'事件,因为浏览器中的任何事件都是异步的,并被推送到事件循环/队列中。

以下是一个非常基本的示例,用于使用来测试click事件fakeAsync

fakeAsync功能通过在特殊的fakeAsync测试区域中运行测试主体来实现线性编码样式。

在这里,我正在测试由click事件调用的方法。

it('should', fakeAsync( () => {
    fixture.detectChanges();
    spyOn(componentInstance, 'method name'); //method attached to the click.
    let btn = fixture.debugElement.query(By.css('button'));
    btn.triggerEventHandler('click', null);
    tick(); // simulates the passage of time until all pending asynchronous activities finish
    fixture.detectChanges();
    expect(componentInstance.methodName).toHaveBeenCalled();
}));

以下是Angular文档要说的内容:

fakeAsync相对于async的主要优点是测试似乎是同步的。不会then(...)破坏可见的控制流。兑现承诺的fixture.whenStable方式已经消失,取而代之的是tick()

局限性。例如,您不能从fakeAsync


有什么理由偏爱该解决方案或公认的解决方案吗?作为一个有角的菜鸟,我不知道哪个“更好”
Adam Hughes

2
根据角度文档,@ AdamHughes fakeAsync更易于阅读和理解。但是您可以选择最适合自己的。使用aync或fakeAsync时,没有什么比被接受的解决方案好。
Mav55 '17

1
在这里写了一个关于使用asyncvs.的比较的答案fakeAsync。即使回答中使用async,总的来说,我还是更喜欢使用fakeAsync
保罗·萨姆索塔

tick()如果您的代码正在调用setTimeout,则只应包含s ,我认为不需要打勾?参见stackoverflow.com/a/50574080/227299
Juan Mendes

我更喜欢这种解决方案,因为它使用debugElement(这是与环境无关的选择器)。
JoshuaTree

10

我正在使用Angular 6。我遵循了Mav55的回答,并且有效。但是我想确定是否fixture.detectChanges();确实有必要,所以我将其删除,但它仍然有效。然后,我移开tick();查看它是否有效。最终,我从fakeAsync()包装中取出了测试,并且感到惊讶,它奏效了。

所以我最终得到了这个:

it('should call onClick method', () => {
  const onClickMock = spyOn(component, 'onClick');
  fixture.debugElement.query(By.css('button')).triggerEventHandler('click', null);
  expect(onClickMock).toHaveBeenCalled();
});

而且效果很好。


我的一般策略是始终调用fixture.detectChanges()以触​​发angular的生命周期事件。可能是您的组件没有ngOnInit或不需要通过测试。
SnailCoil

@SnailCoil或者他可以使用自动变化检测(angular.io/guide/testing#automatic-change-detection
Maksymilian Majer

1
某些dom事件是同步的。例如,记录输入事件为同步。如果您对按钮单击事件的体验为true,则意味着按钮单击事件也是同步的。但是,有许多Dom事件是异步的,在这种情况下,您需要使用asyncfakeAsync。为了安全起见,假设所有事件都是异步的并且仅使用asyncor fakeAsync(这是您的偏好),我不会发现任何错误。
Paul Samsotha

如果您未在完整的浏览器中运行,则最好使用By.css而不是querySelector。(srcstackoverflow.com/questions/44400566/…)。
Ambroise Rabier

@PaulSamsotha用tick()s乱码似乎是个坏主意,因为某些东西可能是异步的?您应该知道什么是异步的,什么不是异步的。触发鼠标/键盘/输入事件始终是同步的。
Juan Mendes

1

我遇到了类似的问题(以下详细说明),并且jasmine-core: 2.52通过使用tick与原始函数相同(或更长时间)的函数来解决了该问题(在中)setTimeout调用中。

例如,如果我有一个setTimeout(() => {...}, 2500);(因此它将在2500毫秒后触发),我将致电tick(2500)这将解决问题。

我在组件中所拥有的内容,作为对Delete按钮的反应,单击:

delete() {
    this.myService.delete(this.id)
      .subscribe(
        response => {
          this.message = 'Successfully deleted! Redirecting...';
          setTimeout(() => {
            this.router.navigate(['/home']);
          }, 2500); // I wait for 2.5 seconds before redirect
        });
  }

她是我的工作考验:

it('should delete the entity', fakeAsync(() => {
    component.id = 1; // preparations..
    component.getEntity(); // this one loads up the entity to my component
    tick(); // make sure that everything that is async is resolved/completed
    expect(myService.getMyThing).toHaveBeenCalledWith(1);
    // more expects here..
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    const deleteButton = fixture.debugElement.query(By.css('.btn-danger')).nativeElement;
    deleteButton.click(); // I've clicked the button, and now the delete function is called...

    tick(2501); // timeout for redirect is 2500 ms :)  <-- solution

    expect(myService.delete).toHaveBeenCalledWith(1);
    // more expects here..
  }));

PSfakeAsync可以在这里找到关于测试的大量解释和一般异步信息:有关使用Angular 2进行测试策略的视频-朱莉·拉尔夫(Julie Ralph),从8:10开始,持续4分钟:)


1

首先要检查按钮调用事件,我们需要监视在单击按钮后将被调用的方法,因此我们的第一行将是spyOn spy方法接受两个参数1)组件名称2)被监视的方法,即:'onSubmit'记住不要使用' ()仅需要名称,然后我们需要使按钮的对象被单击,现在我们必须触发事件处理程序,在该事件处理程序上添加click事件,然后期望我们的代码一次调用Submit方法

it('should call onSubmit method',() => {
    spyOn(component, 'onSubmit');
    let submitButton: DebugElement = 
    fixture.debugElement.query(By.css('button[type=submit]'));
    fixture.detectChanges();
    submitButton.triggerEventHandler('click',null);
    fixture.detectChanges();
    expect(component.onSubmit).toHaveBeenCalledTimes(1);
});

似乎不适合我。我正在使用Angular7。我认为正确的方法是: it('should call onSubmit method',() => { let mock = spyOn(component, 'onSubmit'); let submitButton: DebugElement = fixture.debugElement.query(By.css('button[type=submit]')); fixture.detectChanges(); submitButton.triggerEventHandler('click',null); fixture.detectChanges(); expect(mock).toHaveBeenCalledTimes(1); });
Todor Todorov,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.