Angular 中的单元测试点击事件

问题描述 投票:0回答:6

我正在尝试将单元测试添加到我的 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
时是否发生日志记录;我还想测试当用户单击组件 HTML 文件中定义的编辑按钮时是否调用
onEditButtonClick
。我怎样才能做到这一点?

angular typescript jasmine
6个回答
155
投票

我的目标是检查当用户单击编辑按钮时是否调用“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();

}));

不要忘记导入

fakeAsync
tick

另请参阅:


58
投票

可以使用

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

下面是一个非常基本的示例,使用

fakeAsync
测试点击事件。

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

这里我正在测试一个由点击事件调用的方法。

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

中进行 XHR 调用

14
投票

我正在使用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();
});

而且效果很好。


2
投票

首先要检查按钮调用事件,我们需要监视单击按钮后将调用的方法 所以我们的第一行是spyOn 间谍方法有两个参数 1) 元件名称 2)要监视的方法,即:“onSubmit”记住不要使用“()”,仅需要名称 然后我们需要制作要单击的按钮对象 现在我们必须触发事件处理程序,我们将在其上添加单击事件 那么我们期望我们的代码调用一次提交方法

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);
});

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..
  }));

附注关于

fakeAsync
和测试中一般异步的精彩解释可以在这里找到:关于 Angular 2 测试策略的视频 - Julie Ralph,从 8:10 开始,持续 4 分钟:)


0
投票

如果您没有可供查询的 css 类,您可以对适合您情况的属性使用函数匹配。

it('should call onClick from button element', fakeAsync(() => {
  spyOn(component, 'onClick').and.callFake(() => null);

  fixture.detectChanges();

  const button = fixture.debugElement.query(
    (val) => val.attributes['id'] === 'button-1'
  );

  expect(button).toBeTruthy();

  trashCanButton.children[0].triggerEventHandler('click', {});

  tick(1000);

  expect(component.onClick).toHaveBeenCalledTimes(1);
}));
© www.soinside.com 2019 - 2024. All rights reserved.