如何使用 jest 和 react-testing-library 测试上下文提供的函数?
How can I test functions that are provided by context using jest and react-testing-library?
我有一个 <UserProvider />
组件提供上下文,它的值中有一个 login
方法。在我的测试中,我想检查是否已调用该方法。如果此方法来自上下文,我如何检查以确保在我的测试中调用此方法?我试过使用 spyOn and mock 方法,但我无法使它们起作用。
这是我的 UserProvider
组件,它提供上下文。
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 }
这是我的 Login
组件,使用来自 UserProvider
的上下文(通过 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
这是我的 Login
测试
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)
})
我有一个 codesandbox 但它出错了 jest.mock 不是一个函数所以我不知道它是否非常有用。
如果不稍微更改源代码,我无法让它工作。
首先,我必须导出实际上下文
export { UserProvider, useUser, UserContext }
我们可以使用模拟的 login
函数重新创建提供程序,以下测试将为您服务。
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)
});
这完全有可能不是最好的方法。希望对你有帮助。
我在这里看到另外 2 种方法:
- 将
Context.Consumer
与被测组件一起注入。然后你就可以验证它
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"
});
- Mock calls behind
UserContext.Consumer
(让它成为投机 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);
对我来说,第二个更好,而第一个依赖于实现细节(上下文数据的结构)。
PS 对于第一种方法,您可以在测试中正确声明消费者组件以避免导出 UserContext
:
function ContextHelper({ spy }) {
const contextData = useUser();
spy(contextData);
return null;
}
...
const contextCallback = jest.fn();
const { getByText } = render(
<UserProvider>
<Login />
<ContextHelper spy={contextCallback} />
</UserProvider>
);
但请记住,spy
的调用次数可能会超出您的预期。
我有一个 <UserProvider />
组件提供上下文,它的值中有一个 login
方法。在我的测试中,我想检查是否已调用该方法。如果此方法来自上下文,我如何检查以确保在我的测试中调用此方法?我试过使用 spyOn and mock 方法,但我无法使它们起作用。
这是我的 UserProvider
组件,它提供上下文。
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 }
这是我的 Login
组件,使用来自 UserProvider
的上下文(通过 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
这是我的 Login
测试
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)
})
我有一个 codesandbox 但它出错了 jest.mock 不是一个函数所以我不知道它是否非常有用。
如果不稍微更改源代码,我无法让它工作。
首先,我必须导出实际上下文
export { UserProvider, useUser, UserContext }
我们可以使用模拟的 login
函数重新创建提供程序,以下测试将为您服务。
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)
});
这完全有可能不是最好的方法。希望对你有帮助。
我在这里看到另外 2 种方法:
- 将
Context.Consumer
与被测组件一起注入。然后你就可以验证它
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"
});
- Mock calls behind
UserContext.Consumer
(让它成为投机LoginAPI
call inUserContext
)
jest.mock("../LoginAPI.js");
...
const { getByText } = render(
<UserProvider>
<Login />
</UserProvider>
);
const submitButton = getByText('Login');
fireEvent.click(submitButton);
expect(LoginAPI.login).toHaveBeenCalledTimes(1);
对我来说,第二个更好,而第一个依赖于实现细节(上下文数据的结构)。
PS 对于第一种方法,您可以在测试中正确声明消费者组件以避免导出 UserContext
:
function ContextHelper({ spy }) {
const contextData = useUser();
spy(contextData);
return null;
}
...
const contextCallback = jest.fn();
const { getByText } = render(
<UserProvider>
<Login />
<ContextHelper spy={contextCallback} />
</UserProvider>
);
但请记住,spy
的调用次数可能会超出您的预期。