2

I am trying to test the error case for the function removeBuilding(), in which the API call fails. I am mocking the API call to error to enter the catch block, which then calls displayError(). My test is expecting to see displayError getting called and sees none, but the function is running and printing to console, so for some reason jest simply cannot tell it is getting called.

function being tested:

removeBuilding = index => {
    let { allBuildings } = this.state;
    const buildings = this.state.allBuildings.filter(building => building.campus_id === this.props.selectedCampus);
    if (index >= 0 && index <= buildings.length - 1) {
      const building = buildings[index];
      axios
        .delete(`/api/buildings/${building.id}`)
        .then(() => {
          allBuildings = allBuildings.filter(function(value) {
            return value !== building;
          });
          this.setState({
            allBuildings,
          });
        })
        .catch(error => {
          // Test successfully reaches this point! 
          this.props.displayError(error);
        });
    }
  };

test:

it.only('fails to make axios delete call, and displays error', () => {
      let displayErrorMock = jest.fn(console.log("testing"));
      wrapper = shallowWithIntl(
        <AddBuildingList 
          displayError={displayErrorMock}
        />
      );
      deleteSpy = jest.spyOn(axios, 'delete').mockRejectedValueOnce(error);
      wrapper.instance().setState({ allBuildings : [newBuilding] });
      wrapper.instance().removeBuilding(0);
      expect(displayErrorMock).toHaveBeenCalled();
    });
 });

failed test output:

 expect(jest.fn()).toHaveBeenCalled()
Expected number of calls: >= 1                                                                                                                                                                                     
Received number of calls:    0
184 |       wrapper.instance().setState({ allBuildings : [newBuilding] });
185 |       wrapper.instance().removeBuilding(0);
> 186 |       expect(displayErrorMock).toHaveBeenCalled();
|                                ^
187 |     });
188 |   });
189 | });
at Object.<anonymous> (tests/Components/AddBuildingList/AddBuildingList.test.js:186:32)

console.log tests/Components/AddBuildingList/AddBuildingList.test.js:177
testing                                                              

EDIT I figured out the problem using this answer. Basically, since the axios call returns a promise, the code in the .then and .catch doesnt execute until the promise is resolved(or rejected), So the expect(..) was happening before the function call it was expecting. It was solved by defining

const flushPromises = () => new Promise(setImmediate)

and putting

await flushPromises();

right before expect statements that were testing for a function getting called in .then and .catch

The reason this works this way has to do with the setImmediate behavior, and the way multiple promises are handled in the javascript microtasks queue

Cameron K
  • 43
  • 1
  • 6

1 Answers1

0

Function mock is incorrect, in order for a function to call a console on call, it should be:

jest.fn(() => console.log("testing"))

This is not needed to confirm that it was called this way because toBeCalled assertion provides a guarantee that it was called, as long as it's evident that a correct function is asserted.

The test should be async, displayErrorMock isn't supposed to be called at the time it's asserted. removeBuilding is untestable because it doesn't return a promise that could be chained in a test. It should contain :

return axios
...

Then in tests it can be awaited:

await wrapper.instance().removeBuilding(0);
expect(displayErrorMock).toHaveBeenCalled();
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    I tried adding the return statement and the async/await, but it is still receiving no calls. Whats weird is that I have an almost identical test for addBuilding() that is passing – Cameron K Sep 12 '20 at 18:20