4

我正在尝试测试角度分量的输出。

我有一个复选框组件,它使用 EventEmitter 输出其值。复选框组件包含在故事书故事中,用于演示和测试目的:

export const basic = () => ({
  moduleMetadata: {
    imports: [InputCheckboxModule],
  },
  template: `
<div style="color: orange">
 <checkbox (changeValue)="changeValue($event)" [selected]="checked" label="Awesome">
 </checkbox>
</div>`,
  props: {
    checked: boolean('checked', true),
    changeValue: action('Value Changed'),
  },
});

我正在使用一个动作来捕获值变化并将其记录到屏幕上。

然而,在为这个组件编写 cypress e2e 时,我只使用 iFrame 而不是整个故事书应用程序。

我想找到一种方法来测试输出是否正常。我尝试在 iFrame 中的 postMessage 方法上使用间谍,但这不起作用。

 beforeEach(() => {
      cy.visit('/iframe.html?id=inputcheckboxcomponent--basic', {
        onBeforeLoad(win) {
          cy.spy(window, 'postMessage').as('postMessage');
        },
      });
    });

然后断言将是:

  cy.get('@postMessage').should('be.called');

有没有其他方法可以断言(changeValue)="changeValue($event)" 已经解雇?

4

3 回答 3

3

方法一:模板

我们可以将最后发出的值绑定到模板并检查它。

{
  moduleMetadata: { imports: [InputCheckboxModule] },
  template: `
   <checkbox (changeValue)="value = $event" [selected]="checked" label="Awesome">
   </checkbox>
  
   <div id="changeValue">{{ value }}</div> <!-- ❗️ -->
 `,
}
it("emits `changeValue`", () => {
 // ...

 cy.get("#changeValue").contains("true"); // ❗️
});

方法2:窗口

我们可以将最后发出的值分配给全局window对象,在赛普拉斯中检索它并验证该值。

export default {
  title: "InputCheckbox",
  component: InputCheckboxComponent,
  argTypes: {
    selected: { type: "boolean", defaultValue: false },
    label: { type: "string", defaultValue: "Default label" },
  },
} as Meta;


const Template: Story<InputCheckboxComponent> = (
  args: InputCheckboxComponent
) =>
  ({
    moduleMetadata: { imports: [InputCheckboxModule] },
    component: InputCheckboxComponent,
    props: args,
  } as StoryFnAngularReturnType);


export const E2E = Template.bind({});
E2E.args = {
  label: 'E2e label',
  selected: true,
  changeValue: value => (window.changeValue = value), // ❗️
};

it("emits `changeValue`", () => {
  // ...

  cy.window().its("changeValue").should("equal", true); // ❗️
});

方法 3:角度

我们可以使用存储在全局命名空间中的Angular 函数ng来获取对 Angular 组件的引用并监视输出。

⚠️注意:

  • ng.getComponent()仅当 Angular 在开发模式下运行时可用。即enableProdMode()不调用。
  • 设置process.env.NODE_ENV = "development";.storybook/main.js防止 Storybook 在 prod 模式下构建 Angular(请参阅源代码)。
export const E2E = Template.bind({});
E2E.args = {
  label: 'E2e label',
  selected: true,
  // Story stays unchanged
};

describe("InputCheckbox", () => {
  beforeEach(() => {
    cy.visit(
      "/iframe.html?id=inputcheckboxcomponent--e-2-e",
      registerComponentOutputs("checkbox") // ❗️
    );
  });

  it("emits `changeValue`", () => {
    // ...

    cy.get("@changeValue").should("be.calledWith", true); // ❗️
  });
});
function registerComponentOutputs(
  componentSelector: string
): Partial<Cypress.VisitOptions> {
  return {
    // https://docs.cypress.io/api/commands/visit.html#Provide-an-onLoad-callback-function
    onLoad(win) {
      const componentElement: HTMLElement = win.document.querySelector(
        componentSelector
      );
      // https://angular.io/api/core/global/ngGetComponent
      const component = win.ng.getComponent(componentElement);

      // Spy on all `EventEmitters` (i.e. `emit()`) and create equally named alias
      Object.keys(component)
        .filter(key => !!component[key].emit)
        .forEach(key => cy.spy(component[key], "emit").as(key)); // ❗️
    },
  };
}

概括

  • 我喜欢在方法 1 中没有魔法。它易于阅读和理解。不幸的是,它需要指定一个带有用于验证输出的附加元素的模板。
  • 方法 2 的优点是我们不再需要指定模板。但是我们需要为每个@Output我们想要测试的额外代码添加。此外,它使用全局window来“交流”。
  • Apprach 3 也不需要模板。它的优点是 Storybook 代码(故事)不需要任何调整。我们只需要传递一个参数cy.visit()(很可能已经使用)就可以执行检查了。因此,如果我们想通过 Storybook 的iframe. 最后但同样重要的是,我们检索对 Angular 组件的引用。有了这个,我们还可以直接在组件本身上调用方法或设置属性。这ng.applyChanges似乎为额外的测试用例打开了一些大门。
于 2021-03-22T12:23:15.660 回答
0

您正在监视window.postMessage(),这是一种启用窗口对象(弹出窗口、页面、iframe 等)之间的跨域通信的方法。

Storybook 中的 iFrame 不会将任何消息传递给另一个窗口对象,但您可以在应用程序上安装 Kuker 或其他外部 Web 调试器来监视两者之间的消息,从而使 Cypress spy 方法工作。

如果您选择在 Angular 应用程序上安装 Kuker,请按照以下步骤操作:

npm install -S kuker-emitters

添加 Kuker Chrome 扩展程序以使其正常工作。

于 2020-08-18T10:49:57.527 回答
0

如果您使用的是cypress-storybook包和@storybook/addon-actions,则有一种方法可用于此用例,在我看来,它提供了最简单的解决方案。

使用 Storybook-actions 插件,您可以像这样声明您的 @Output 事件

export default {
    title: 'Components/YourComponent',
    component: YourComponent,
    decorators: [
        moduleMetadata({
            imports: [YourModule]
        })
     ]
 } as Meta;

const Template: Story<YourStory> = (args: YourComponent) => ({
   props: args
 });

export const default = Template.bind({});
default.args = {
  // ...
  changeValue: action('Value Changed'), // action from @storybook/addon-actions
};

在您的 cypress 测试中,您现在可以调用该cy.storyAction()方法并将期望语句应用于它。

it('should execute event', () => {
  // ...
  cy.storyAction('Value Changed').should('have.been.calledWith', 'value');
})
于 2021-07-29T14:17:19.913 回答