使用 Storybook 和 Cypress 测试角度组件 @Output
Posted
技术标签:
【中文标题】使用 Storybook 和 Cypress 测试角度组件 @Output【英文标题】:Test angular component @Output using Storybook and Cypress 【发布时间】:2020-12-07 07:12:06 【问题描述】:我正在尝试测试角度组件的输出。
我有一个复选框组件,它使用 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)"
开除了吗?
【问题讨论】:
您在监视错误的窗口。试试cy.spy(win, 'postMessage').as('postMessage');
【参考方案1】:
方法一:模板
我们可以将最后发出的值绑定到模板并检查它。
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"); // ❗️?
);
方法二:窗口
我们可以将最后发出的值分配给全局 window
对象,在 Cypress 中检索它并验证该值。
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:角度
我们可以使用存储在ng
下的全局命名空间中的Angular's functions 来获取对Angular 组件的引用并监视输出。
⚠️注意:
ng.getComponent()
仅在 Angular 在开发模式下运行时可用。 IE。 enableProdMode()
未被调用。
在 .storybook/main.js
中设置 process.env.NODE_ENV = "development";
以防止 Storybook 在 prod 模式下构建 Angular(请参阅 source)。
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
的结合似乎为其他测试用例打开了一些大门。
【讨论】:
【参考方案2】:您正在监视window.postMessage()
,这是一种启用窗口对象(弹出窗口、页面、iframe 等)之间跨域通信的方法。
Storybook 中的 iFrame 不会将任何消息传递给另一个窗口对象,但您可以在应用程序上安装 Kuker 或其他外部 Web 调试器来监视两者之间的消息,从而使 Cypress spy 方法正常工作。
如果您选择在您的 Angular 应用程序上安装 Kuker,请按照以下步骤操作:
npm install -S kuker-emitters
添加 Kuker Chrome 扩展程序以使其工作。
【讨论】:
【参考方案3】:如果您使用的是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');
)
【讨论】:
以上是关于使用 Storybook 和 Cypress 测试角度组件 @Output的主要内容,如果未能解决你的问题,请参考以下文章
Cypress系列- Cypress 编写和组织测试用例篇 之 .skip() 和 .only() 的详细使用
在 react native 中使用 jest 和 Storybook 快照测试时出现问题
Cypress 组件测试、ReactJS 和 TailwindCSS
TypeError:使用 React、Storybook 和 Storyshots 插件进行快照测试时,提供的值不是“元素”类型