我正在尝试在 cypress 测试中检查 API 调用是否只进行一次。
然后我尝试继续进行更改,并且我想验证这些调用实际上并未进行。
it("update statement from the map is edited and sent only once", () => {
cy.intercept("POST", mapsPath, { fixture: "initDefaultMap.json" }).as("saveMap");
cy.intercept("POST", lrsPath, { fixture: "statementOpened.json" }).as("statementUpdatedMap");
cy.get('[data-cy="canvas"]').click();
cy.get("zt-color-picker[icon='bg-color']").shadow().find("zt-button").shadow().find(".zt-button").click();
cy.get("zt-color-picker[icon='bg-color']")
.shadow()
.find("#color-picker-submenu > ul > li.color-boxes[data-color='#99FFCC']")
.click();
cy.wait("@statementUpdatedMap").then((evt) => expect(extractVerb(evt)).to.be.equal("updated"));
cy.wait("@saveMap");
cy.intercept("POST", lrsPath, cy.spy());
cy.get('[data-cy="edit-title"]').click();
cy.get("body [data-cy='input-title'] input").clear().type("mappa bella");
cy.get('[data-cy="canvas"]').click();
cy.wait(1000).then(() => expect(cy.spy()).not.to.be.called);
cy.get("zt-color-picker[icon='bg-color']").shadow().find("zt-button").shadow().find(".zt-button").click();
cy.get("zt-color-picker[icon='bg-color']")
.shadow()
.find("#color-picker-submenu > ul > li.color-boxes[data-color='#5533FF']")
.click();
cy.wait(1000).then(() => expect(cy.spy()).not.to.be.called);
});
我尝试使用
cy.spy()
,但没有成功
不幸的是,
cy.intercept()
不允许直接验证呼叫发生的固定次数。
如果我通过
POST
发送 cy.window()
来模拟您的代码,当我等待第二个从未发生的调用时,我可以看到测试失败:
const lrsPath = 'endpoint' // for example
cy.intercept("POST", lrsPath, { fixture: "statementOpened.json" })
.as("statementUpdatedMap");
// trigger the post (simulated)
cy.window().then(win => {
win.fetch(lrsPath, { method: "POST" })
})
cy.wait("@statementUpdatedMap") // wait for one call
cy.wait("@statementUpdatedMap") // wait for another call
然后测试失败:
我可以通过验证是否引发了失败消息来将其转变为通过测试。
cy.on('fail')
捕获错误done()
函数来确保抛出错误(Mocha done()
是 it()
函数的参数)我使用以下自定义命令
Cypress.Commands.add('verifyFailed', (expectedMessage, done) => {
function handleFail(error) {
const actual = error.message
const passed = actual.includes(expectedMessage)
const log = passed ? 'Correct error thrown' :
`"${expected}", actual: "${actual}"`
assert(passed, log)
cy.off('fail', handleFail)
done()
}
cy.on('fail', handleFail)
})
然后我在测试失败时使用它,在本例中是在第二个
cy.wait('@statementUpdatedMap')
之前。
it('turn a failing test into a passing test with Mocha done()', (done) => {
const lrsPath = 'endpoint' // for example
cy.intercept("POST", lrsPath, { fixture: "statementOpened.json" })
.as("statementUpdatedMap");
// trigger the post (simulated)
cy.window().then(win => {
win.fetch(lrsPath, { method: "POST" })
})
cy.wait("@statementUpdatedMap") // wait for one call
cy.verifyFailed('No request ever occurred', done)
cy.wait("@statementUpdatedMap") // wait for another call
})
如果发生第二次调用确实,则测试将失败。
使用
done()
功能可防止出现 误报 结果。
cy.get('@statementUpdatedMap.all')
是当前有效的语法,尽管如上所述尚未正式记录。它已经存在了一段时间,所以很有可能它会继续可用。
它的问题是,您只能在确定所有呼叫都有响应后才能运行它(
cy.intercept()
记录呼叫的响应阶段)。
因此您可以在调用之前添加等待,但有一些常见的警告:
cy.wait(2000)
cy.get('@statementUpdatedMap.all') ...
在这个特定的示例中,
cy.intercepts()
都是存根的,因此添加等待可能是可以的 - 没有直接的网络参与来破坏测试。
话虽如此,在 CI 上运行仍然可能会导致失败,因为云服务器正在处理多个虚拟实例的多任务,并且可能需要几秒钟的时间来进行任务切换,因此在该环境中这可能不是一个好主意。
还有另一种使用 https://github.com/bahmutov/cypress-network-idle 的方法看起来很有用,但仍然需要指定等待时间。
有一个功能待处理的呼叫,看起来像是在观看请求而不是响应,这才是你真正想做的。 (自述文件页面上的链接已损坏,因此我直接链接到测试)。
cy.waitForNetworkIdle('@all', 2000, { timeout: 6000 })
.should('have.keys', 'started', 'finished', 'waited', 'callCount')
.then(({ waited, callCount }) => {
// the page makes the Ajax call that resolves after 3 seconds
// with 2 seconds of checking for network idle makes 5 seconds
expect(waited, 'waited ms').to.be.within(5000, 6500)
// the document and the Ajax
expect(callCount, 'callCount').to.equal(2)
})
如果您想在不使用插件的情况下使用这个想法,这就是我在基本 Cypress 代码中的做法
let calls = 0;
cy.intercept("POST", mapsPath, (req) => {
// record the call upon request (should happen very soon after the 2nd click
calls++;
// reply with fixture as per original code
req.reply({
fixture: 'initDefaultMap.json'
})
}).as("saveMap");
... // initial code to set up the click
cy.wait("@saveMap"); // wait for the first call
... // repeat code to click again
cy.get(something-that-appears-on-the-page-after-clicking)
.should('be.visible') // preferred way to wait
.then(() => {
expect(calls).to.eq(1)
})
cy.verifyFailed()
命令看起来非常可靠,并且不仅仅可以用于验证拦截调用。
cy.intercept()
查询别名(通过
cy.as()
)的
cy.get()
调用。我们可以将其与 (记录不足) @alias.all
请求结合起来,以获取使用别名的整个次数列表(在本例中为触发拦截的次数),并使用该结果的长度来验证该电话只打过一次。
假设
statementUpdatedMap
别名是您要验证的调用仅被调用一次...
it("update statement from the map is edited and sent only once", () => {
cy.intercept("POST", mapsPath, { fixture: "initDefaultMap.json" }).as("saveMap");
cy.intercept("POST", lrsPath, { fixture: "statementOpened.json" }).as("statementUpdatedMap");
cy.get('[data-cy="canvas"]').click();
cy.get("zt-color-picker[icon='bg-color']").shadow().find("zt-button").shadow().find(".zt-button").click();
cy.get("zt-color-picker[icon='bg-color']")
.shadow()
.find("#color-picker-submenu > ul > li.color-boxes[data-color='#99FFCC']")
.click();
cy.wait("@statementUpdatedMap").then((evt) => expect(extractVerb(evt)).to.be.equal("updated"));
cy.wait("@saveMap");
cy.get('[data-cy="edit-title"]').click();
cy.get("body [data-cy='input-title'] input").clear().type("mappa bella");
cy.get('[data-cy="canvas"]').click();
cy.get('@statementUpdatedMap.all').should('have.length', 1); // validating length of yielded calls is one
cy.get("zt-color-picker[icon='bg-color']").shadow().find("zt-button").shadow().find(".zt-button").click();
cy.get("zt-color-picker[icon='bg-color']")
.shadow()
.find("#color-picker-submenu > ul > li.color-boxes[data-color='#5533FF']")
.click();
cy.get('@statementUpdatedMap.all').should('have.length', 1); // validating length of yielded calls is one });