I have a <UserProvider />
component that provides context which has a login
method in its value. In my test I want to check if that method has been called. How can I check to make sure this method is being called in my test if it comes from context? I've tried using the spyOn and mock methods but I can't get them to work.
Here's my UserProvider
component which provides the context.
import React, { useState, useContext, createContext } from 'react'
const UserContext = createContext()
function UserProvider({ children }) {
const [user, setUser] = useState(null)
const login = user => setUser(user)
return <UserContext.Provider value={{ user, login }}>{children}</UserContext.Provider>
}
const useUser = () => useContext(UserContext)
export { UserProvider, useUser }
Here's my Login
component using the context from UserProvider
(via useUser
)
import React from 'react'
import { useUser } from './UserProvider'
function Login() {
const { login } = useUser()
const handleSubmit = async e => {
e.preventDefault()
// I need to make sure this method is being called in my test.
login({ name: 'John Doe' })
}
return (
<form onSubmit={handleSubmit}>
<button>Login</button>
</form>
)
}
export default Login
Here's my Login
test
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Login from './Login'
import { UserProvider, useUser } from './UserProvider'
// Is this correct?
jest.mock('./UserProvider', () => ({
useUser: jest.fn(() => ({
login: jest.fn()
}))
}))
it('should log a user in', () => {
const { getByText } = render(
<UserProvider>
<Login />
</UserProvider>
)
const submitButton = getByText(/login/i)
fireEvent.click(submitButton)
// How can I make this work?
const { login } = useUser()
expect(login).toHaveBeenCalledTimes(1)
})
I have a codesandbox but it's erroring out about jest.mock not being a function so I don't know if it's very useful.
I can't get it to work without slightly change the source code a little bit.
First, I have to export the actual context
export { UserProvider, useUser, UserContext }
We can re create provider with a mocked login
function and the following test will serve you purpose.
import React from 'react'
import { render, fireEvent } from '@testing-library/react'
import Login from './Login'
import { UserProvider, useUser, UserContext } from './UserProvider'
it('should log a user in', () => {
const login = jest.fn();
const { getByText } = render(
<UserContext.Provider value={{ login }}>
<Login />
</UserContext.Provider>
);
const submitButton = getByText('Login');
fireEvent.click(submitButton);
expect(login).toHaveBeenCalledTimes(1)
});
It is entirely possible that this is not the best approach. I hope it helps.
I see 2 more approaches here:
Context.Consumer
alongside your component under test. Then so you would be able to verify against it const contextCallback = jest.fn();
const { getByText } = render(
<UserProvider>
<Login />
<UserContext.Consumer>{contextCallback}</UserContext.Consumer>
</UserProvider>
);
const submitButton = getByText('Login');
fireEvent.click(submitButton);
expect(contextCallback.mock.calls[0][0]).toEqual({
user: "John Doe"
});
UserContext.Consumer
(let it be speculative LoginAPI
call in UserContext
) jest.mock("../LoginAPI.js");
...
const { getByText } = render(
<UserProvider>
<Login />
</UserProvider>
);
const submitButton = getByText('Login');
fireEvent.click(submitButton);
expect(LoginAPI.login).toHaveBeenCalledTimes(1);
To me 2nd one is better while 1st rely on implementation details(context data's structure).
PS to 1st approach you may declare consumer component right in the test to avoid exporting UserContext
:
function ContextHelper({ spy }) {
const contextData = useUser();
spy(contextData);
return null;
}
...
const contextCallback = jest.fn();
const { getByText } = render(
<UserProvider>
<Login />
<ContextHelper spy={contextCallback} />
</UserProvider>
);
But keep in mind that spy
may be called more times that you'd expect.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.