Javascript 测试反应组件设置状态过载,该过载具有以下功能

Javascript 测试反应组件设置状态过载,该过载具有以下功能,javascript,reactjs,jestjs,Javascript,Reactjs,Jestjs,我试图测试一个React组件,它使用setState的一个重载,但不确定如何正确断言调用。一个例子是: class CounterComponent extends React.Component { updateCounter() { this.setState((state) => { return { counterValue: state.counterValue + 1 };

我试图测试一个React组件,它使用setState的一个重载,但不确定如何正确断言调用。一个例子是:

class CounterComponent extends React.Component {

    updateCounter() {
        this.setState((state) => {
            return {
                counterValue: state.counterValue + 1
            };
        });
    }
}
这里的假设是,该方法将被异步调用,因此除了对setState的调用之外,不能依赖于当前状态(因为它可能在setState执行之前发生更改)。有人能建议你如何坚持这个要求吗?以下测试失败,因为它只是比较函数名

it("Should call setState with the expected parameters", () => {
            const component = new CounterComponent();

            component.setState = jest.fn(() => {});
            component.state = { counterValue: 10 };

            component.updateCounter();

            const anonymous = (state) => {
                return { 
                    counterValue: state.counterValue + 1
                };
            };

            //expect(component.setState).toHaveBeenCalledWith({ counterValue: 11 });
            expect(component.setState).toHaveBeenCalledWith(anonymous);
        });
编辑:鉴于yohai在下面的回答,我将添加一些进一步的上下文,因为我觉得我可能过度简化了问题,但是为了清晰起见,我不想重新编写整个问题

在我的实际组件中,正在编辑的状态值不是一个简单的数字,而是一个具有以下结构的对象数组:

{ isSaving: false, hasError: false, errorMessage: ''}
和其他一些属性。当用户单击“保存”时,将为数组中的每个项激发一个异步操作,然后在该操作返回或被拒绝时更新相应的条目。例如,save方法如下所示:

onSave() {
    const { myItems } = this.state;

    myItems.forEach(item => {
        api.DoStuff(item)
            .then(response => this.handleSuccess(response, item))
            .catch(error => this.handleError(error, item));
    });
}
it("Should call setState with the expected parameters", () => {

    let updateStateFunction = null;
    const component = new CounterComponent();
    component.setState = jest.fn((func) => { updateStateFunction = func;});

    component.updateCounter();

    const originalState = { counterValue: 10 };
    const expectedState =  { counterValue: 11};

    expect(component.setState).toHaveBeenCalled();
    expect(updateStateFunction(originalState)).toEqual(expectedState);
});
handle success和error方法仅更新对象并调用replaceItem:

handleSuccess(response, item) {
    const updated = Object.assign({}, item, { hasSaved: true });
    this.replaceItem(updated);
}

handleError(error, item) {
    const updated = Object.assign({}, item, { hasError: true });
    this.replaceItem(updated);
}
然后替换数组中的项:

   replaceItem(updatedItem) {
        this.setState((state) => {
            const { myItems } = state;
            const working = [...myItems];

            const itemToReplace = working.find(x => x.id == updatedItem.id);

            if (itemToReplace) {
                working.splice(working.indexOf(itemToReplace), 1, updatedItem);
            };

            return {
                myItems: working
            };
        });
    }
replaceItem是我尝试测试的方法,我尝试验证它是否使用正确的重载和正确更新状态的函数调用setState

下面我的回答详细说明了我是如何解决这个问题的,但欢迎评论和回答=)

@Vallerii:测试结果状态似乎是一种更简单的方法,但是如果我这样做,测试就无法知道方法没有这样做:

replaceItem(updatedItem) {
    const { myItems } = state;
    const working = [...myItems];

    const itemToReplace = working.find(x => x.id == updatedItem.id);

    if (itemToReplace) {
        working.splice(working.indexOf(itemToReplace), 1, updatedItem);
    };

    this.setState({ myItems: working });
}

当replaceItem没有为setState使用正确的重载时,当反复调用此代码时失败,因为(我假设)react正在批处理更新,并且此版本使用的状态是过时的。

经过进一步思考,我找到了解决此问题的方法。我不确定这是否是最好的解决方案,但考虑到上面示例中的
updateCounter
方法将函数传递到
setState
调用中,我可以简单地获取该函数的引用,使用已知状态执行该函数,并检查返回值是否正确

结果测试如下所示:

onSave() {
    const { myItems } = this.state;

    myItems.forEach(item => {
        api.DoStuff(item)
            .then(response => this.handleSuccess(response, item))
            .catch(error => this.handleError(error, item));
    });
}
it("Should call setState with the expected parameters", () => {

    let updateStateFunction = null;
    const component = new CounterComponent();
    component.setState = jest.fn((func) => { updateStateFunction = func;});

    component.updateCounter();

    const originalState = { counterValue: 10 };
    const expectedState =  { counterValue: 11};

    expect(component.setState).toHaveBeenCalled();
    expect(updateStateFunction(originalState)).toEqual(expectedState);
});

我认为你应该测试一些不同的东西,它看起来会像这样(我用的是酶):

从“React”导入React
从“酶”导入{mount}
从“./counterponent”导入counterponent
它(“应将状态增加1”,()=>{
常量组件=挂载()
常数计数器=10;
setState({counter});
component.instance().updateCounter();
expect(component.state().counter).toEqual(counter+1);
});

我不明白你为什么要首先测试
setState
。第三方库应该自己测试代码,尤其是React。因此,基本上你可以假设它能按预期工作,除非上游错误引起了麻烦,而这不是你的错。我不是在测试setState,我是在测试updateCounter调用setState时使用的参数是否正确。@DavidMcNee,你不必检查是否调用了setState。您需要检查状态是否已设置。@DavidMcNee您以一种奇怪的方式测试您的组件。了解酶。为什么需要计数器值?假设您在div({this.state.counter})中显示这个值,那么您需要检查这个div的文本是否等于您期望的值。就像@Valerii所说的,您应该测试结果,而不是内部实现。如果将来您将更改代码实现,则测试仍应为“每次更新passAs”,此解决方案不会验证是否调用了正确的setState重载。测试将通过,但一旦引入react的setState批处理,这将不再有效。