【问题标题】:Cannot test AsyncTypeahead from react-bootstrap-typeahead with Enzyme无法使用酶从 react-bootstrap-typeahead 测试 AsyncTypeahead
【发布时间】:2020-02-22 13:55:25
【问题描述】:

我正在尝试从 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 被模拟为返回两个元素)

当我在我的故事书中运行它时,它看起来很好:

但如果我想测试它(使用 Enzyme),它无法识别弹出的 项。

    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..."

没有找到两个“下拉项”项,而是只有一项带有文本“键入要搜索...”。

AynchTypeahead 是否没有正确更新关于 Enzyme 的 DOM?

【问题讨论】:

    标签: enzyme react-bootstrap-typeahead fetch-mock


    【解决方案1】:

    &lt;AsyncTypeahead&gt; 是异步。另一方面,simulate() 是同步的。因此,当您到达 expect() AsyncTypeahead 时,甚至还没有开始使用 &lt;li&gt; 元素填充下拉列表。你需要等待它。

    未指定,但看起来您正在使用fetch-mock 包。 有flush函数

    返回一个 Promise,一旦 fetch-mock 处理的所有提取都已解决,该 Promise 就会解决

    所以这个:

    ...
    
    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()
    

    没有。如果没有fetch 的调用,await fetchMock.flush() 会立即返回。而且可能不会有。因为&lt;AsyncTypeahead&gt; 去抖动。

    (顺便说一句,您也可以尝试在每个测试的基础上模拟 fetch。以防万一。)

    所以我看到了两个选项:

    1. 使用其他东西代替fetch-mock 包。在模拟请求完成时,您可以在其中解决自己的 Promises。
    2. 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 而不更改实际代码(这是非常不可取的)。

    【讨论】:

    • 是的,你是对的。我正在使用 fetch-mock。我确实尝试了你的建议,但我仍然没有任何运气......尝试了其他一些事情,但 async 关键字似乎是关键
    • 我想我需要对 onSearch 回调中定义的 fetch 进行某种等待
    • 是的。但可悲的是,这需要为了测试而更改您的真实代码。
    • 是的……我也是这么想的。 :(
    • 也许是另一个模拟库? github.com/jefflau/jest-fetch-mock#functions 看起来你可以用它定义你自己等待的承诺。
    【解决方案2】:

    我的问题的确切解决方案在以下代码中(复制并粘贴到 JS 文件中以查看它的工作)。

    注意事项:

    • 我需要使用 async-wait-until 库中的 waitUntil 函数。 fetch-mock 本身不提供测试异步代码的功能。
    • 我需要在 global.document.createRange 中添加一个丑陋的 hack,因为 react-bootstrap-typeahead 和 jest 存在一些工具提示问题。
    • 使用 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
    

    这可能更好,因为这意味着我不需要了解组件内部。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-18
      • 1970-01-01
      • 2018-06-14
      • 2018-10-25
      • 2016-08-20
      • 2017-05-03
      • 2019-02-05
      • 1970-01-01
      相关资源
      最近更新 更多