无法使用酶从 react-bootstrap-typeahead 测试 AsyncTypeahead
Cannot test AsyncTypeahead from react-bootstrap-typeahead with Enzyme
我正在尝试从 react-bootstrap-typeahead.
测试 AsyncTypeahead
我有一个非常简单的测试组件:
class AsyncTypeahead2 extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
};
}
render() {
return ( <AsyncTypeahead
isLoading={this.state.isLoading}
onSearch={query => {
this.setState({isLoading: true});
fetch("http://www.myHTTPenpoint.com")
.then(resp => resp.json())
.then(json => this.setState({
isLoading: false,
options: json.items,
}));
}}
options={this.state.options}
labelKey={option => `${option.stateName}`}
/> )
}
}
const url = "http://www.myHTTPenpoint.com"
fetchMock
.reset()
.get(
url,
{
items: [
{id:1, stateName:"Alaska"},
{id:2, stateName:"Alabama"}
]
},
);
(注意URL被mock成了return两个元素)
当我 运行 在我的故事书中看到它时,它看起来不错 :
但如果我想测试它(使用酶),它无法识别弹出的 < li > 项目。
let Compoment =
<div>Basic AsyncTypeahead Example
<AsyncTypeahead2/>
</div>
const wrapper = mount(Compoment);
let json = wrapper.html();
let sel = wrapper.find(".rbt-input-main").at(0)
sel.simulate('click');
sel.simulate('change', { target: { value: "al" } });
expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
expect(wrapper.find(".dropdown-item").length).toBe(2) //but get just 1 element "Type to Search..."
没有找到两个 "dropdown-item" 项,而是只有一个包含文本 "Type to Search...".
的项
AynchTypeahead 是否没有根据酶正确更新 DOM?
<AsyncTypeahead>
是 异步的 。另一方面 simulate()
是 同步的 。因此,当您到达 expect()
AsyncTypeahead
时,甚至还没有开始使用 <li>
元素填充下拉列表。您需要等待。
未指定,但看起来您正在使用 fetch-mock
包。
flush
函数
Returns a Promise that resolves once all fetches handled by fetch-mock have resolved
所以这个:
...
sel.simulate('click');
sel.simulate('change', { target: { value: "al" } });
await fetchMock.flush() // !!!
expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
expect(wrapper.find(".dropdown-item").length).toBe(2)
应该可以。
...但可能不会。因为
fetchMock.mock(...)
fetch(...)
await fetchMock.flush()
确实有效,但是
fetchMock.mock(...)
setTimeout(() => fetch(...), 0)
await fetchMock.flush()
没有。 await fetchMock.flush()
returns 如果没有 fetch
的调用,马上。而且可能不会有。因为 <AsyncTypeahead>
去抖动。
(顺便说一句,您也可以尝试在每次测试的基础上模拟 fetch
。以防万一。)
所以我看到两个选项:
- 使用其他东西代替
fetch-mock
包。在模拟请求完成时,您可以在哪里解决自己的 Promises
。
- https://tech.travelaudience.com/how-to-test-asynchronous-data-fetching-on-a-react-component-ff2ee7433d71
import waitUntil from 'async-wait-until';
...
test("test name", async () => {
let Compoment = <AsyncTypeahead2/>
...
await waitUntil(() => wrapper.state().isLoading === false);
// or even
// await waitUntil(() => wrapper.find(".dropdown-item").length === 2, timeout);
expect(...)
})
这个选项如果不漂亮。但也许这是您唯一的选择 - 您应该担心的不只是 fetch-mock
。 setState
也是异步的...而且看起来没有很好的方法来检查何时完成更新状态和 DOM
而不更改真实代码(这是非常不可取的)。
我的问题的确切解决方案在以下代码中(复制并粘贴到 JS 文件中以查看它是否有效)。
注意事项:
- 我需要使用 async-wait-until 库中的 waitUntil 函数。 fetch-mock 本身不提供测试异步代码的功能。
- 由于 react-bootstrap-typeahead 和 jest 的一些工具提示问题,我需要在 global.document.createRange 添加一个丑陋的 hack。
- 使用 waitUntil 等待组件内部状态的变化
- 之后调用wrapper.update()更新DOM非常重要。
..
import React, {Component} from 'react';
import waitUntil from 'async-wait-until';
import {mount} from "enzyme";
import fetchMock from "fetch-mock";
import {AsyncTypeahead} from "react-bootstrap-typeahead";
describe('Autocomplete Tests ', () => {
test(' Asynch AutocompleteInput ', async () => {
class AsyncTypeaheadExample extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
finished: false
};
}
render() {
return (<AsyncTypeahead
isLoading={this.state.isLoading}
onSearch={query => {
this.setState({isLoading: true});
fetch("http://www.myHTTPenpoint.com")
.then(resp => resp.json())
.then(json => this.setState({
isLoading: false,
options: json.items,
finished: true
}));
}}
options={this.state.options}
labelKey={option => `${option.stateName}`}
/>)
}
}
const url = "http://www.myHTTPenpoint.com"
fetchMock
.reset()
.get(
url,
{
items: [
{id: 1, stateName: "Alaska"},
{id: 2, stateName: "Alabama"}
]
},
);
let Compoment =
<AsyncTypeaheadExample/>
// ugly hacky patch to fix some tooltip bug
// https://github.com/mui-org/material-ui/issues/15726
global.document.createRange = () => ({
setStart: () => {
},
setEnd: () => {
},
commonAncestorContainer: {
nodeName: 'BODY',
ownerDocument: document,
},
});
let wrapper = mount(Compoment);
let sel = wrapper.find(".rbt-input-main").at(0)
sel.simulate('click');
sel.simulate('change', {target: {value: "al"}});
expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
//now the async stuff is happening ...
await waitUntil(() => {
return wrapper.state().finished === true;
}, 3000); //wait about 3 seconds
wrapper.update() //need to update the DOM!
expect(wrapper.find(".dropdown-item").length).toBe(2) //but get just 1 element "Type to Search..."
})
});
更新
我也可以比较包装项目而不是直接比较状态:
//now the async stuff is happening ...
await waitUntil(() => {
wrapper.update() //need to update the DOM!
return wrapper.find(".dropdown-item").length > 1
}, 3000); //wait about 3 seconds
这可能更好,因为这意味着我不需要了解组件内部。
我正在尝试从 react-bootstrap-typeahead.
测试 AsyncTypeahead我有一个非常简单的测试组件:
class AsyncTypeahead2 extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
};
}
render() {
return ( <AsyncTypeahead
isLoading={this.state.isLoading}
onSearch={query => {
this.setState({isLoading: true});
fetch("http://www.myHTTPenpoint.com")
.then(resp => resp.json())
.then(json => this.setState({
isLoading: false,
options: json.items,
}));
}}
options={this.state.options}
labelKey={option => `${option.stateName}`}
/> )
}
}
const url = "http://www.myHTTPenpoint.com"
fetchMock
.reset()
.get(
url,
{
items: [
{id:1, stateName:"Alaska"},
{id:2, stateName:"Alabama"}
]
},
);
(注意URL被mock成了return两个元素)
当我 运行 在我的故事书中看到它时,它看起来不错 :
但如果我想测试它(使用酶),它无法识别弹出的 < li > 项目。
let Compoment =
<div>Basic AsyncTypeahead Example
<AsyncTypeahead2/>
</div>
const wrapper = mount(Compoment);
let json = wrapper.html();
let sel = wrapper.find(".rbt-input-main").at(0)
sel.simulate('click');
sel.simulate('change', { target: { value: "al" } });
expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
expect(wrapper.find(".dropdown-item").length).toBe(2) //but get just 1 element "Type to Search..."
没有找到两个 "dropdown-item" 项,而是只有一个包含文本 "Type to Search...".
的项AynchTypeahead 是否没有根据酶正确更新 DOM?
<AsyncTypeahead>
是 异步的 。另一方面 simulate()
是 同步的 。因此,当您到达 expect()
AsyncTypeahead
时,甚至还没有开始使用 <li>
元素填充下拉列表。您需要等待。
未指定,但看起来您正在使用 fetch-mock
包。
flush
函数
Returns a Promise that resolves once all fetches handled by fetch-mock have resolved
所以这个:
...
sel.simulate('click');
sel.simulate('change', { target: { value: "al" } });
await fetchMock.flush() // !!!
expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
expect(wrapper.find(".dropdown-item").length).toBe(2)
应该可以。
...但可能不会。因为
fetchMock.mock(...)
fetch(...)
await fetchMock.flush()
确实有效,但是
fetchMock.mock(...)
setTimeout(() => fetch(...), 0)
await fetchMock.flush()
没有。 await fetchMock.flush()
returns 如果没有 fetch
的调用,马上。而且可能不会有。因为 <AsyncTypeahead>
去抖动。
(顺便说一句,您也可以尝试在每次测试的基础上模拟 fetch
。以防万一。)
所以我看到两个选项:
- 使用其他东西代替
fetch-mock
包。在模拟请求完成时,您可以在哪里解决自己的Promises
。 - https://tech.travelaudience.com/how-to-test-asynchronous-data-fetching-on-a-react-component-ff2ee7433d71
这个选项如果不漂亮。但也许这是您唯一的选择 - 您应该担心的不只是import waitUntil from 'async-wait-until'; ... test("test name", async () => { let Compoment = <AsyncTypeahead2/> ... await waitUntil(() => wrapper.state().isLoading === false); // or even // await waitUntil(() => wrapper.find(".dropdown-item").length === 2, timeout); expect(...) })
fetch-mock
。setState
也是异步的...而且看起来没有很好的方法来检查何时完成更新状态和DOM
而不更改真实代码(这是非常不可取的)。
我的问题的确切解决方案在以下代码中(复制并粘贴到 JS 文件中以查看它是否有效)。
注意事项:
- 我需要使用 async-wait-until 库中的 waitUntil 函数。 fetch-mock 本身不提供测试异步代码的功能。
- 由于 react-bootstrap-typeahead 和 jest 的一些工具提示问题,我需要在 global.document.createRange 添加一个丑陋的 hack。
- 使用 waitUntil 等待组件内部状态的变化
- 之后调用wrapper.update()更新DOM非常重要。
..
import React, {Component} from 'react';
import waitUntil from 'async-wait-until';
import {mount} from "enzyme";
import fetchMock from "fetch-mock";
import {AsyncTypeahead} from "react-bootstrap-typeahead";
describe('Autocomplete Tests ', () => {
test(' Asynch AutocompleteInput ', async () => {
class AsyncTypeaheadExample extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
finished: false
};
}
render() {
return (<AsyncTypeahead
isLoading={this.state.isLoading}
onSearch={query => {
this.setState({isLoading: true});
fetch("http://www.myHTTPenpoint.com")
.then(resp => resp.json())
.then(json => this.setState({
isLoading: false,
options: json.items,
finished: true
}));
}}
options={this.state.options}
labelKey={option => `${option.stateName}`}
/>)
}
}
const url = "http://www.myHTTPenpoint.com"
fetchMock
.reset()
.get(
url,
{
items: [
{id: 1, stateName: "Alaska"},
{id: 2, stateName: "Alabama"}
]
},
);
let Compoment =
<AsyncTypeaheadExample/>
// ugly hacky patch to fix some tooltip bug
// https://github.com/mui-org/material-ui/issues/15726
global.document.createRange = () => ({
setStart: () => {
},
setEnd: () => {
},
commonAncestorContainer: {
nodeName: 'BODY',
ownerDocument: document,
},
});
let wrapper = mount(Compoment);
let sel = wrapper.find(".rbt-input-main").at(0)
sel.simulate('click');
sel.simulate('change', {target: {value: "al"}});
expect(wrapper.find(".rbt-input-main").at(0).getElement().props.value).toBe("al")
//now the async stuff is happening ...
await waitUntil(() => {
return wrapper.state().finished === true;
}, 3000); //wait about 3 seconds
wrapper.update() //need to update the DOM!
expect(wrapper.find(".dropdown-item").length).toBe(2) //but get just 1 element "Type to Search..."
})
});
更新
我也可以比较包装项目而不是直接比较状态:
//now the async stuff is happening ...
await waitUntil(() => {
wrapper.update() //need to update the DOM!
return wrapper.find(".dropdown-item").length > 1
}, 3000); //wait about 3 seconds
这可能更好,因为这意味着我不需要了解组件内部。