reactjs - 您如何使用反应测试库测试临时加载文本?
问题描述
我有一个表格,在处理时显示“提交”状态,完成后显示“已提交”状态。
这是我正在使用的提交处理程序...
const handleSubmit = (e, _setTitle) => {
e.preventDefault()
_setTitle('Submitting...')
try {
doformStuff(emailRef.current.value)
} catch (err) {
throw new Error(err)
} finally {
_setTitle('Submitted perfectly.')
}
}
我想测试提交状态是否出现,无论多么简短。
it('shows "submitting" when submitting', async () => {
// arrange
render(<MobileEmailCapture/>)
// act
const emailInput = screen.getByPlaceholderText('yourem@il.com')
userEvent.type(emailInput, fakeEmail)
fireEvent.submit(screen.getByTestId('form'))
// assert
expect(screen.getByTestId('title')).toHaveTextContent('Submitting...')
})
问题是测试直接跳转到提交状态
Error: FAILED Expected 'Submitted perfectly.' to match 'Submitting...'.
我知道这就是它结束的地方,但我想测试临时过渡状态。我怎么做?
解决方案
我想出了一个解决这个问题的方法,尽管它确实需要一些解释。我将从一些原始但抽象的代码开始,并在将其与您的特定问题相关联时对其进行解释。
“delayWhile”解决方案
import {waitFor} from "@testing-library/react"
describe('Button should', () => {
let sideEffect = NO_SIDE_EFFECT
let lock = {locked: true}
beforeEach(() => {
sideEffect = NO_SIDE_EFFECT
})
afterEach(() => {
lock.locked = false
})
it("Side effect not applied", async () => {
changeStateLater()
await waitFor(() => expect(sideEffect).toBe(NO_SIDE_EFFECT))
})
it("Side effect applied", async () => {
changeStateLater()
applyChangeOfState()
await waitFor(() => expect(sideEffect).toBe(SIDE_EFFECTED))
})
async function changeStateLater() {
await delayWhile(() => lock.locked)
sideEffect = SIDE_EFFECTED
}
function applyChangeOfState() {
lock.locked = false
}
})
async function delayWhile(condition: () => boolean, timeout = 2000) {
const started = performance.now();
while (condition()) {
const elapsed = performance.now() - started;
if (elapsed > timeout) throw Error("delayWhile lock was never released");
await syncronousDelay();
}
}
function syncronousDelay (n: number = 0) {
return new Promise((done: any) => {
setTimeout(() => { done() }, n)
})
}
const NO_SIDE_EFFECT = "no side effect"
const SIDE_EFFECTED = "side effected"
好的,这可能比我需要显示的代码更多。我想给你一个完整的工作示例,这样,如果你和我一样需要看到一些工作并弄乱它以理解它,你可以自己测试它。
这里的想法是我们生成一个异步锁,并且总是在 beforeEach 内部生成一个新的锁定锁。“changeStateLater”代表您想要在完成之前调用和测试的任何异步调用(在您的情况下为“doformStuff”)。这个函数会调用“delayWhile”,然后你给它发送一个带有锁值的函数。delayWhile 在同步延迟上循环直到锁被解决。由于每次清空调用堆栈时都会调用此验证,因此您可以随时确定地解锁它(就像我在“applyChangeOfState”上所做的那样)。由于这将在调用堆栈为空时调用,如果您要直接评估结果,则可能由于尚未应用更改而失败,因此您应该将期望包装在 waitFor 或使用 findBy react 查询。最后,afterEach 位确保我们将其解锁,以便它始终可以整齐地退出循环,而不是超时。
如何将其应用于您的用例
首先,doformStuff 需要是异步的,你需要在handleSubmit 中等待它。否则,您将永远不会显示中间状态。
您可能想在测试中模拟 doformStuff 并在其中使用我的异步锁“delayWhile”。一个简单的方法是让该函数成为渲染组件的依赖项,但如果这不可行,您也可以将该函数移动到一个单独的文件中并从那里调用它。在测试中,您可以执行以下操作:
import * as foo from "@/whatever_your_file_is";
describe("Bar", () => {
const doformStuffSpy = jest.spyOn(foo, "doformStuff")
let api_lock = { locked: true }
beforeEach(() => {
// refresh the lock object
api_lock = { locked: true }
doformStuffSpy.mockReset()
doformStuffSpy.mockReturnValue(Promise.resolve())
});
afterEach(() => {
// clear the lock so that it does not keep polling until timeout
api_lock.locked = false
})
it('shows "submitting" when submitting', async () => {
// arrange
render(<MobileEmailCapture/>)
// act
const emailInput = screen.getByPlaceholderText('yourem@il.com')
userEvent.type(emailInput, fakeEmail)
// this will lock doformStuff and not let it return until we say so
doformStuffSpy.mockImplementation(() => delayWhile(() => api_lock.locked))
fireEvent.submit(screen.getByTestId('form'))
// assert
expect(screen.getByTestId('title')).toHaveTextContent('Submitting...')
})
function call_this_if_you_want_to_unlock_it() {
api_lock.locked = false
}
}
async function delayWhile(condition: () => boolean, timeout = 2000) {
const started = performance.now();
while (condition()) {
const elapsed = performance.now() - started;
if (elapsed > timeout) throw Error("delayWhile lock was never released");
await syncronousDelay();
}
}
function syncronousDelay (n: number = 0) {
return new Promise((done: any) => {
setTimeout(() => { done() }, n)
})
}
我希望这有点清楚,如果不是,请告诉我,我很乐意解决您可能遇到的任何问题。
奖金简单版
如果你想要一个更简单但更脆弱的实现,你可以在模拟函数中添加一些同步延迟。这将使您免于使用 delayWhile。只需替换前面示例中的以下行并删除 api_lock:
//doformStuffSpy.mockImplementation(() => delayWhile(() => api_lock.locked))
doformStuffSpy.mockReturnValue(new Promise(resolve => setTimeout(resolve, 2000)))
当然,这具有不确定性的问题。尽管 2 秒(或任何类似的值)对于您必须运行的测试逻辑来说应该足够了,但感觉有点奇怪。如果“delayWhile”解决方案对于您的用例来说似乎有点过于庞大,您仍然可以使用它。
最终结果将类似于:
import * as foo from "@/whatever_your_file_is";
describe("Bar", () => {
const doformStuffSpy = jest.spyOn(foo, "doformStuff")
beforeEach(() => {
doformStuffSpy.mockReset()
doformStuffSpy.mockReturnValue(Promise.resolve())
});
it('shows "submitting" when submitting', async () => {
// arrange
render(<MobileEmailCapture/>)
// act
const emailInput = screen.getByPlaceholderText('yourem@il.com')
userEvent.type(emailInput, fakeEmail)
// this will lock doformStuff for 2 seconds, should be enough to make our assertions
doformStuffSpy.mockReturnValue(new Promise(resolve => setTimeout(resolve, 2000)))
fireEvent.submit(screen.getByTestId('form'))
// assert
expect(screen.getByTestId('title')).toHaveTextContent('Submitting...')
})
}
推荐阅读
- jquery - 使用 CSS 和 Jquery 平滑过渡
- electron - 电子用户安装程序构建
- python - 如何使 Swarmplot(Seaborn)中的点相互重叠?
- python - Apscheduler 可以使用相同的 cron 触发器运行两个作业吗?
- apache - virtualhost 只能访问 localhost
- oauth-2.0 - Google Adwords API with OAuth2 如何获取授权用户的电子邮件?
- google-cloud-platform - 谷歌云:如何列出授予用户或服务帐户的权限?
- excel - 使用 VB.NET 将 CSV 转换为 XLSX:分隔符错误
- excel - 在表中查找特定值
- java - 通过 Jenkins 运行时未捆绑 Jhipster/spring boot 静态资源 pdf 文件