0

So I have a Classical component in React, RecipesNew, that has the following initial state:

        this.state = {
            recipe: {
                title: '',
                description: '',
                prepTime: '',
                cookingTime: ''
            },
            errors: false
        }

It has a form and the following method for onSubmit:

        handleOnSubmit = (e) => {
            e.preventDefault()
    
            this.props.startAddRecipe(recipe)
                .then(recipe => this.props.history.push(`/recipes/${recipe._id}`))
                .catch(err => this.setState({ errors: err }))
        }

The startAddRecipe method on props rerturns a promise which resolves when an axios call within it, to my db, resolves and then dispathces an action to update the redux state. The promise rejects when the axios call within it rejects.

I want to test startAddRecipe for when the startAddRecipe rejects and sets this.state.errors.

Within my tests file I have wrttien a function that returns a promise and rejects straight away to pass to RecipesNew as the startAddRecipe prop:

const startAddRecipeRejectsSpy = jest.fn((recipeData = {}) => {
    return Promise.reject(['Test Error', 'Test Error 2']) 
})

This is my test to test when the Promise rejects. I assumed the state.errors would be populated with this array : ['Test Error', 'Test Error 2']

test('startAddRecipe rejects correctly', () => {

    const wrapper = shallow(<RecipesNew startAddRecipe={startAddRecipeRejectsSpy} />);
    const mockRecipe = {
        title: 'Test Title',
        description: 'Test Description',
        prepTime: 10,
        cookingTime: 10
    }
    wrapper.setState({ recipe: mockRecipe })
    wrapper.find('form').simulate('submit',{ preventDefault: () => { } })
    expect(startAddRecipeRejectsSpy).toHaveBeenLastCalledWith(mockRecipe)
    expect(startAddRecipeRejectsSpy(mockRecipe)).rejects.toEqual(['Test Error', 'Test Error 2'])
    expect(wrapper.update().state().errors).toBe(['Test Error', 'Test Error 2']);
    expect(wrapper.update()).toMatchSnapshot();
})

Everything works apart from the last 2.

The second to last says:

    Expected: ["Test Error", "Test Error 2"]
    Received: false

So basically the state appears to not be changing?

Finally the snap shot in the last one is not what I expected as when this.state.errors is not falsey I map over the array of errors and produce a list - this list does not appear in snaphot.

Any ideas? Ive read lots of threads on this but nothing seems to match my issue.

Any help appreciated.

Thanks!

skyboyer
  • 22,209
  • 7
  • 57
  • 64
rhetoric
  • 49
  • 6
  • Does your `handleOnSubmit()` catch any errors? – k-wasilewski Jul 15 '20 at 17:34
  • yes, when ive tested it manually, for example not had certain required fields in the data sent to the endpoint of my API, it returns the relevant error messages, sets them to state and then they are displayed in the component. – rhetoric Jul 16 '20 at 10:49

1 Answers1

0

startAddRecipe is asynchronous, so it's likely not completing before the test finishes. You need to wait for startAddRecipe to finish before checking the state. You can make sure all promises are flushed before checking the state by doing something like this.

const tick = async () => {
  return new Promise((resolve) => {
    setTimeout(resolve, 0);
  });
};

.
.

test('startAddRecipe rejects correctly', async () => {

    const wrapper = shallow(<RecipesNew startAddRecipe={startAddRecipeRejectsSpy} />);
    const mockRecipe = {
        title: 'Test Title',
        description: 'Test Description',
        prepTime: 10,
        cookingTime: 10
    }
    wrapper.setState({ recipe: mockRecipe })
    wrapper.find('form').simulate('submit',{ preventDefault: () => { } })
    await tick();
    expect(startAddRecipeRejectsSpy).toHaveBeenLastCalledWith(mockRecipe)
    expect(startAddRecipeRejectsSpy(mockRecipe)).rejects.toEqual(['Test Error', 'Test Error 2'])
    expect(wrapper.update().state().errors).toBe(['Test Error', 'Test Error 2']);
    expect(wrapper.update()).toMatchSnapshot();
})

Give that a shot and let me know if it works for you.

jvgaeta
  • 93
  • 4
  • Hi, thanks for that. I gave that a go and I got the same error as I got yesterday when I tried to introduce async/await into the test: `ReferenceError: regeneratorRuntime is not defined` Any ideas? – rhetoric Jul 16 '20 at 10:53
  • fixed my issue above by doing this [https://stackoverflow.com/questions/53558916/babel-7-referenceerror-regeneratorruntime-is-not-defined/61517521#61517521] but now when I run my test it says `TypeError: Cannot read property 'call' of undefined` and this is the only refrence to 'call' I can find in the error stack trace `at ShallowWrapper.call (node_modules/enzyme/src/ShallowWrapper.js:743:7)` Any help appreciated! :-) – rhetoric Jul 16 '20 at 11:21