如何验证使用Jest和Enzyme在componentDidMount中调用console.log?

时间:2019-02-21 03:08:48

标签: javascript reactjs jestjs axios enzyme

我正在尝试测试axios调用未获得200的HTTP响应的情况。当axios未获得成功的响应时,将引发错误。在这种情况下,我想验证console.log是否被调用了两次。

这是我正在测试的课程的摘要:

class App extends React.Component {
    ...  
    async componentDidMount() {
        let url = "/api/info/tmp"
        try {
            let response = await axios.get(url);
            ...do stuff
            this.setState(...);
        } catch (e) {
            console.log("Could not get " + url);
            console.log(e);
        }
    }
    ...
}

这是我开玩笑的片段

let mockAxios = new MockAdapter(axios);
...
describe("App - componentDidMount() behavior test", () => {
    beforeEach(() => {
        app = shallow(<App />);
    })

    afterEach(() => {
        app = undefined;
        mockAxios.reset();
    });
    ...
    describe("Get " + url + " HTTP response status is not 200", () => {
        beforeAll(() => {
            mockAxios.onGet(url).reply(302, mockData);
        });
        it("Does not set state regardless of response body", () => {
            console.log = jest.fn();
            const state = app.state();
            expect(console.log).toHaveBeenCalledTimes(2);
            expect(state.solutions).toEqual({});
            expect(state.username).toEqual("");
        });
    });
});

我知道console.log = jest.fn()位正在做什么,因为在设置它时控制台不再记录伪造的错误。但是,测试失败,因为Expected mock function to have been called two times, but it was called zero times.

我尝试将console.log = jest.fn()移到“ beforeEach”,“ beforeAll”中并作为全局变量。

更新

我很确定这与所有正在进行的异步有关。 如果我这样做:

    it("Does not set state regardless of response body", async () => {
        console.log = jest.fn();
        await app.instance().componentDidMount();
        expect(console.log).toHaveBeenCalledTimes(2);

        const state = app.state();
        expect(state.solutions).toEqual({});
        expect(state.username).toEqual("");
    });

然后测试仍然失败,但是我的原因改变了:Expected mock function to have been called two times, but it was called four times.现在,我只想弄清楚为什么调用它四次而不是两次。

更新2

我弄清楚了为什么console.log被调用了4次!现在,我只需要弄清楚如何重构测试。 如果我注释掉我的笑话模拟,甚至是整个单元测试

    it("Does not set state regardless of response body", async () => {
        //const state = app.state();
        //expect(state.solutions).toEqual({});
        //expect(state.username).toEqual("");
        //expect(console.log).toHaveBeenCalledTimes(2);
    });

然后我可以在控制台中算出确实已经有两个不同的console.log调用。 shallow(<App />)必须已经在呼叫componentDidMount()或其他东西。当我添加app.instance().componentDidMount()时,可以直观地看到它正在记录4次。

3 个答案:

答案 0 :(得分:2)

更新的答案

由于您似乎已经知道您在使用模拟程序,所以问题可能与componentDidMount()有关。

我相信您对shallow(<App />)的呼叫将已经呼叫App的componentDidMount()一次(这意味着您的console.log将被呼叫两次)。

然后,您随后致电app.instance().componentDidMount()-即再次致电componentDidMount()()(这意味着您的console.log将再次被致电两次)。< / p>

因此,总共...有4个呼叫console.log

希望能为您指明正确的方向...

原始答案

实际上,您的问题看起来与[关于如何"How to mock console when it is used by a third-party library?"的StackOverFlow问题

您可以使用Jest mock functionsspyOn对象global.console

例如,您的测试可能如下所示:


// Setup jest to spy on the console
const consoleSpy = jest.spyOn(global.console, 'log')

describe('App - componentDidMount() behavior test', () => {
  beforeEach(() => {
    jest.resetAllMocks()  // reset your consoleSpy state back to initial
    app = shallow(<App />)
  })

  ...

      it('Does not set state regardless of response body', () => {
        const spy = jest.spyOn(global.console, 'log')
        const state = app.state()
        expect(consoleSpy).toHaveBeenCalledTimes(2)
        expect(state.solutions).toEqual({})
        expect(state.username).toEqual('')
      })
  ...

答案 1 :(得分:1)

理想情况下,将API调用移至componentDidMount之外,并移入其自己的类method中。可以通过生命周期方法或事件回调手动调用它。另外,您应该预期响应会以某种方式影响您的UI state(例如:向用户显示请求失败的消息,然后重试)。

以下示例可以用.then/.catch代替async/await完成。无论哪种方式,您都在使用Promises的{​​{1}},因此他们需要asynchronous测试。

注意:以下假设asynchronousdisableLifecycleMethods适配器中为true。另外,仅测试enzyme的更改(或state)有点多余;相反,您将测试是否根据当前的console.log渲染了一个组件。

工作示例https://codesandbox.io/s/939w229l9r(包括stateend to end测试---您可以通过点击integration来运行测试标签位于沙箱左下角附近)


App.js (这将是一个container,其中包含所有相关的Tests,并根据需要将其分散到其state中)

children

App.test.js (假设您要进行import React, { Component } from 'react'; class App extends Component { state = = { error: "", isLoading: true, solutions: {}, username: "" }; componentDidMount() { this.fetchData("/api/info/tmp"); } fetchData = async (url) => { try { const res = await axios.get(url); ...do stuff this.setState({ error: "", isLoading: false, solutions: res.data.solutions, username: res.data.username }); } catch (err) { this.setState({ error: err, isLoading: false, solutions: {}, username: "" }); } } render() { ... } } 测试)

end to end

App.test.js (假设您要进行import { shallow } from 'enzyme'; import App from './App'; const timeout = () => new Promise(resolve => { setTimeout(() => { resolve(); }, 2000); }); const initialState = { error: "", isLoading: true, solutions: {}, username: "" }; describe("App", () => { let wrapper; beforeEach(() => { wrapper = shallow(<App />); wrapper.setState({ ...initialState }); }); afterAll(() => { wrapper.unmount(); }); it("sets data to state based upon successful API call", async () => { wrapper.instance().fetchData("/api/info/tmp"); await timeout(); wrapper.update(); expect(wrapper.state('isLoading')).toBeFalsy(); expect(wrapper.state('solutions')).toEqual({ somedata }); expect(wrapper.state('username')).toEqual("Some User"); }); it("displays an error upon unsuccessful API call", async () => { wrapper.instance().fetchData("/api/bad/url"); await timeout(); wrapper.update(); expect(wrapper.state('isLoading')).toBeFalsy(); expect(wrapper.state('solutions')).toEqual({}); expect(wrapper.state('username')).toEqual(""); expect(wrapper.state('error')).toEqual("No data found."); }); }); 测试)

integration

答案 2 :(得分:0)

我知道了!有点...我不确定为什么为什么这样工作,但是在实际的“ it”中设置模拟却不起作用。 解决方案是制作一个beforeEachafterEach

describe("Get " + url + " HTTP response status is not 200", () => {
    beforeAll(() => {
        mockAxios.onGet(url).reply(302, mockData);
    });
    beforeEach(() => {
        console.log = jest.fn();
    });

    afterEach(() => {
        jest.resetAllMocks();
    });
    it("Does not set state regardless of response body", async () => {
        const state = app.state();
        expect(state.solutions).toEqual({});
        expect(state.username).toEqual("");
        expect(console.log).toHaveBeenCalledTimes(2);
    });
});