在不监视组件实例的情况下测试 Angular 输出

Testing Angular Output without spying on the component instance

假设我有以下组件:

@Component({
    selector: 'app-dumb',
    template: '<button (click)="increment()">Increment</button>'
})
export class DumbComponent {
    @Output() onIncrement = new EventEmitter<void>();


    increment() {
        this.onIncrement.emit();
    }
}

我正在使用 Angular Testing Library,我的 objective 是点击按钮并断言给定的输出函数已被调用。

项目正在使用karma/jasmine,添加jest好像不太直接

以下描述了我能够检查所需内容的唯一方法,但我想避免监视 componentInstance,而是注入我想监视的 thing

it("emits an event when the increment button is clicked", async () => {
  const { fixture } = await render(DumbComponent);
  spyOn(fixture.componentInstance.onIncrement, 'emit');
  await clickIncrementButton();
  expect(fixture.componentInstance.onIncrement.emit).toHaveBeenCalledTimes(1);
})

我尝试使用 jasmine.createSpy 但它似乎不是要注入的有效类型。

const onIncrement = createSpy();
await render(DumbComponent, {
    componentProperties: {
        onIncrement: onIncrement
    }
})

有什么想法吗?

长话短说,我可以注入的仅有的两个 things 是事件发射器实例或 mock/spy.

前者可以通过以下方式实现:

// solution 1
it("emits an event when the increment button is clicked", async () => {
    let emitted = false;
    const onIncrement = new EventEmitter<void>();
    await render(DumbComponent, {
        componentProperties: {
            onIncrement: onIncrement
        }
    });
    onIncrement.subscribe(() => emitted = true)
    await clickIncrementButton();
    expect(emitted).toBeTrue();
})

也就是说,正如 jonrsharpe 所指出的,输出是组件 public 接口的一部分,因此实际上我们可以通过直接订阅 public 属性:

// solution 2
it("emits an event when the increment button is clicked", async () => {
    let emitted = false;
    const {fixture} = await render(DumbComponent);
    fixture.componentInstance.onIncrement.subscribe(() => emitted = true)
    await clickIncrementButton();
    expect(emitted).toBeTrue();
})

最后我们还可以通过以下方式注入间谍(jasmine):

// solution 3
it("emits an event when the increment button is clicked", async () => {
    const emit = createSpy()
    await render(DumbComponent, {
        componentProperties: {
            onIncrement: {
                emit: emit
            } as any
        }
    });
    await clickIncrementButton();
    expect(emit).toHaveBeenCalledTimes(1)
})

我个人的偏好是真实实例而不是模拟,具体来说,我可能会坚持使用第一种解决方案。

也就是说,第一个和第二个解决方案是等效的,它们都与 public 接口耦合,因此如果 属性 的名称确实发生了变化,那么这两个测试都需要修复无论如何。

希望对您有所帮助!