1

我正在尝试从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),它无法识别弹出的<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..."

而不是找到两个“下拉项目”项目,只有一个项目带有文本“类型搜索...”。

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

4

2 回答 2

1

我的问题的确切解决方案在以下代码中(复制并粘贴到 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

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

于 2020-03-03T14:14:01.900 回答
1

<AsyncTypeahead>异步的。另一方面simulate()同步的。所以当时你expect() AsyncTypeahead甚至没有开始用<li>元素填充下拉菜单。你需要等待它。

它没有指定,但看起来你正在使用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()

才不是。await fetchMock.flush()如果没有调用 ,则立即返回fetch。而且可能不会有。因为<AsyncTypeahead>去抖动。

(顺便说一句,您也可以尝试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不更改实际代码(这是非常不可取的)。
于 2020-02-28T17:32:03.793 回答