Search code examples
javascriptreactjstestingenzymesinon

How to test a component function being called when componentDidMount?


I have a React JS component MyComponent and I would like to test the following use case:

It should call updateSomething() when component on mount

And I've come up with the following code:

System Under Test (SUT)

export class MyComponent extends React.Component<Props, State> {
    public componentDidMount() {
        console.log("componentDidMount"); // Debug purpose.
        this.fetchSomething()
            .then(() => {
                console.log("fetchSomething"); // Debug purpose.
                this.updateSomething();
            });
    }

    // These are public for simplicity
    public async fetchSomething(): Promise<any> { }
    public updateSomething() {
         console.log("updateSomething"); // Debug purpose.
    }
}

Test

it("should update something, when on mount", () => {
    const props = { ...baseProps };
    sinon.stub(MyComponent.prototype, "fetchSomething").resolves();
    const spy = sinon.spy(MyComponent.prototype, "updateSomething");

    shallow(<MyComponent {...props} />);

    sinon.assert.calledOnce(spy);
});

The result is the test failed with AssertError: expected updateSomething to be called once but was called 0 times but all three console.log() printed.

My understanding is since I want to test the event when on mount, I have to spy/stub it before it's even created, therefore I have to spy on MyComponent.Prototype. Also, for fetchSomething(), I have to stub the async call and make it .resolves() to let it progress.

But I couldn't understand how it can still console.log("updateSomething") without being spied.


Solution

  • According to the comments/answer from this post, the assertion comes before .updateSomething have been called. To solve this problem, I would've to await the componentDidMount lifecycle method.

    So the fixed program is as below:

    // SUT
    
    public async componentDidMount() {
        //...
        return this.fetchSomething()
            .then(() => {
                //...
            });
    }
    
    // Test
    
    it("should update something, when on mount", () => {
        const props = { ...baseProps };
    
        // Disable lifecycle here to enable stub in between.
        const wrapper = shallow(<MyComponent {...props} />, { disableLifecycleMethods: true });
    
        sinon.stub(wrapper.instance(), "fetchSomething").resolves();
        const stub = sinon.stub(wrapper.instance(), "updateSomething");
    
        // Actually call component did mount.
        wrapper.instance().componentDidMount().then(() => {
             sinon.assert.calledOnce(stub);
        });
    });