英文:
How to test custom hook?
问题
Here is the translation of the code portion you provided:
我的自定义钩子:
```javascript
import { useAppDispatch } from "../../app/redux";
import { registrationFormSlice } from "../../entities/registration";
export interface IgetFields {
label: string;
valueKey: string;
setValue: any;
disabled?: boolean;
}
export const useFields = (): (() => IgetFields[][]) => {
const dispatch = useAppDispatch();
const { setEmail } =
registrationFormSlice.actions;
const getFields = (): IgetFields[][] => {
return [
[
{
label: "Email",
valueKey: "email",
setValue: (value: string) =>
dispatch(setEmail({value, error:false, text:''})),
},
],
];
};
return getFields;
};
我的 reducer:
import { initialState } from "./initialState";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { IRegistrationState } from "./models";
export const registrationFormSlice = createSlice({
name: "registrationForm",
initialState,
reducers: {
setEmail(
state: IRegistrationState,
action: PayloadAction<{ value: string; error: boolean; text: string }>
) {
state.data.email.value = action.payload.value;
state.data.email.error = action.payload.error;
state.data.email.errorText = action.payload.text;
},
},
});
我的存储:
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { registrationFormSlice } from "./registration";
const rootReducer = combineReducers({
registrationForm: registrationFormSlice.reducer,
});
export const setupStore = () => {
return configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== "production",
});
};
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore["dispatch"];
我的测试:
import { renderHook } from "@testing-library/react";
import { useFields } from "./data";
import * as useAppDispatch from "../../app/redux";
describe("useFields", () => {
const useDispatchMock = jest.spyOn(useAppDispatch, "useAppDispatch");
useDispatchMock.mockReturnValue(jest.fn());
const setup = () => {
const { result } = renderHook(() => useFields());
return result.current();
};
it("必须是一个数组的数组", () => {
const getFields = setup();
expect(Array.isArray(getFields)).toBeTruthy();
expect(useDispatchMock).toBeCalled();
});
it("索引为 0 的数组必须具有特定的属性和结构", () => {
const getFields = setup()[0];
expect(getFields.length).toBe(1);
expect(typeof getFields[0].label).toBe("string");
expect(getFields[0].valueKey).toBe("email");
expect(typeof getFields[0].setValue).toBe("function");
});
});
一切正常,测试通过,我只是不喜欢测试覆盖率:
我不知道如何测试这行代码
我尝试检查我的 useDispatchMock 被调用时的参数:
import { renderHook } from 'testing-library/react';
import { Provider } from 'react-redux';
import { useFields } from './data';
import { setupStore } from '../../entities/store';
describe('useFields', () => {
const store = setupStore();
const setup = () => {
const { result } = renderHook(useFields, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
return result.current();
};
it('必须是一个数组的数组', () => {
const getFields = setup();
expect(Array.isArray(getFields)).toBeTruthy();
});
it('索引为 0 的数组必须具有特定的属性和结构', () => {
const getFields = setup()[0];
expect(getFields).toHaveLength(1);
expect(getFields[0]).toEqual(
expect.objectContaining({
label: expect.any(String),
valueKey: expect.any(String),
setValue: expect.any(Function),
})
);
});
test('应正确设置电子邮件值', () => {
expect.assertions(1);
const getFields = setup()[0];
const firstField = getFields[0];
store.subscribe(() => {
expect(store.getState().regsitration.data).toEqual({
email: {
value: 'test@example.com',
error: false,
errorText: '',
},
});
});
firstField.setValue('test@example.com');
});
});
但是我得到了一个错误:
[![enter image description here][1]][1]
我的方法是否不好?我如何摆脱这个问题?欢迎任何建议、链接和信息。
谢谢!
<details>
<summary>英文:</summary>
My custom hook:
import { useAppDispatch } from "../../app/redux";
import { registrationFormSlice } from "../../entities/registration";
export interface IgetFields {
label: string;
valueKey: string;
setValue: any;
disabled?: boolean;
}
export const useFields = (): (() => IgetFields[][]) => {
const dispatch = useAppDispatch();
const { setEmail } =
registrationFormSlice.actions;
const getFields = (): IgetFields[][] => {
return [
[
{
label: "Email",
valueKey: "email",
setValue: (value: string) =>
dispatch(setEmail({value, error:false, text:''})),
},
],
];
};
return getFields;
};
My reducer:
import { initialState } from "./initialState";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { IRegistrationState } from "./models";
export const registrationFormSlice = createSlice({
name: "registrationForm",
initialState,
reducers: {
setEmail(
state: IRegistrationState,
action: PayloadAction<{ value: string; error: boolean; text: string }>
) {
state.data.email.value = action.payload.value;
state.data.email.error = action.payload.error;
state.data.email.errorText = action.payload.text;
},
},
});
My Store:
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { registrationFormSlice } from "./registration";
const rootReducer = combineReducers({
registrationForm: registrationFormSlice.reducer,
});
export const setupStore = () => {
return configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== "production",
});
};
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore["dispatch"];
My test:
import { renderHook } from "@testing-library/react";
import { useFields } from "./data";
import * as useAppDispatch from "../../app/redux";
describe("useFields", () => {
const useDispatchMock = jest.spyOn(useAppDispatch, "useAppDispatch");
useDispatchMock.mockReturnValue(jest.fn());
const setup = () => {
const { result } = renderHook(() => useFields());
return result.current();
};
it("Must be an array of arrays", () => {
const getFields = setup();
expect(Array.isArray(getFields)).toBeTruthy();
expect(useDispatchMock).toBeCalled();
});
it("An array with index 0 must have certain properties and structure", () => {
const getFields = setup()[0];
expect(getFields.length).toBe(1);
expect(typeof getFields[0].label).toBe("string");
expect(getFields[0].valueKey).toBe("email");
expect(typeof getFields[0].setValue).toBe("function");
});
});
Everything works, the test passes, I don't like only the test coverage:
[![enter image description here](https://i.stack.imgur.com/uPEIb.png)](https://i.stack.imgur.com/uPEIb.png)
**I do not know how to test this line of code**
I tried to check with which argument my useDispatchMock is called:
import { renderHook } from '@testing-library/react';
import { Provider } from 'react-redux';
import { useFields } from './data';
import { setupStore } from '../../entities/store';
describe('useFields', () => {
const store = setupStore();
const setup = () => {
const { result } = renderHook(useFields, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
return result.current();
};
it('Must be an array of arrays', () => {
const getFields = setup();
expect(Array.isArray(getFields)).toBeTruthy();
});
it('An array with index 0 must have certain properties and structure', () => {
const getFields = setup()[0];
expect(getFields).toHaveLength(1);
expect(getFields[0]).toEqual(
expect.objectContaining({
label: expect.any(String),
valueKey: expect.any(String),
setValue: expect.any(Function),
})
);
});
test('should set email value correctly', () => {
expect.assertions(1);
const getFields = setup()[0];
const firstField = getFields[0];
store.subscribe(() => {
expect(store.getState().regsitration.data).toEqual({
email: {
value: 'test@example.com',
error: false,
errorText: '',
},
});
});
firstField.setValue('test@example.com');
});
});
But I got an error:
[![enter image description here][1]][1]
Is my approach a bad one? What can I do to get rid of this issue? Any suggestion, link and information is welcome.
Thank you in advance!
[1]: https://i.stack.imgur.com/UGMpn.png
</details>
# 答案1
**得分**: 2
以下是您要翻译的内容:
"不要嘲笑`useAppDispatch`,它是一个使用`react-redux`包的hook,使用`useDispatch` hook。错误的嘲笑会破坏其原始实现。相反,我们可以提供一个模拟的存储,并测试Redux操作是否被分发以及Redux状态是否正确更新。
例如,
`registration.ts`:
```ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type IRegistrationState = {
data: {
email: {
value: string;
error: boolean;
errorText: string | null;
};
};
};
export const registrationFormSlice = createSlice({
name: 'registrationForm',
initialState: {
data: {
email: {
value: '',
error: false,
errorText: null,
},
},
},
reducers: {
setEmail(state: IRegistrationState, action: PayloadAction<{ value: string; error: boolean; text: string }>) {
state.data.email.value = action.payload.value;
state.data.email.error = action.payload.error;
state.data.email.errorText = action.payload.text;
},
},
});
store.ts
:
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { useDispatch } from 'react-redux';
import { registrationFormSlice } from './registration';
const rootReducer = combineReducers({
registrationForm: registrationFormSlice.reducer,
});
export const setupStore = () => {
return configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production',
});
};
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore['dispatch'];
export const useAppDispatch: () => AppDispatch = useDispatch;
useFields.ts
:
import { useAppDispatch } from './store';
import { registrationFormSlice } from './registration';
export interface IgetFields {
label: string;
valueKey: string;
setValue: any;
disabled?: boolean;
}
export const useFields = (): (() => IgetFields[][]) => {
const dispatch = useAppDispatch();
const { setEmail } = registrationFormSlice.actions;
const getFields = (): IgetFields[][] => {
return [
[
{
label: 'Email',
valueKey: 'email',
setValue: (value: string) => dispatch(setEmail({ value, error: false, text: '' })),
},
],
];
};
return getFields;
};
useFields.test.tsx
:
import { renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { Provider } from 'react-redux';
import { setupStore } from './store';
import { useFields } from './useFields';
describe('useFields', () => {
const store = setupStore();
const setup = () => {
const { result } = renderHook(useFields, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
return result.current();
};
it('Must be an array of arrays', () => {
const getFields = setup();
expect(Array.isArray(getFields)).toBeTruthy();
});
it('An array with index 0 must have certain properties and structure', () => {
const getFields = setup()[0];
expect(getFields).toHaveLength(1);
expect(getFields[0]).toEqual(
expect.objectContaining({
label: expect.any(String),
valueKey: expect.any(String),
setValue: expect.any(Function),
})
);
});
test('should set email value correctly', () => {
expect.assertions(1);
const getFields = setup()[0];
const firstField = getFields[0];
store.subscribe(() => {
expect(store.getState().registrationForm.data).toEqual({
email: {
value: 'test@example.com',
error: false,
errorText: '',
},
});
});
firstField.setValue('test@example.com');
});
});
测试结果:
PASS stackoverflow/76031614/useFields.test.tsx (7.997 s)
useFields
✓ Must be an array of arrays (14 ms)
✓ An array with index 0 must have certain properties and structure (3 ms)
✓ should set email value correctly (4 ms)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
registration.ts | 100 | 100 | 100 | 100 |
store.ts | 100 | 100 | 100 | 100 |
useFields.ts | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 8.462 s, estimated 10 s
请注意,已经进行了代码的翻译,如有任何其他需要,请告诉我。
英文:
Don't mock useAppDispatch
which is a hook use the useDispatch
hook from the react-redux
package. An incorrect mock will break its original implementation. Instead, we can provide a mock store and test if the redux actions are dispatched and redux state is updated correctly.
E.g.
registration.ts
:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type IRegistrationState = {
data: {
email: {
value: string;
error: boolean;
errorText: string | null;
};
};
};
export const registrationFormSlice = createSlice({
name: 'registrationForm',
initialState: {
data: {
email: {
value: '',
error: false,
errorText: null,
},
},
},
reducers: {
setEmail(state: IRegistrationState, action: PayloadAction<{ value: string; error: boolean; text: string }>) {
state.data.email.value = action.payload.value;
state.data.email.error = action.payload.error;
state.data.email.errorText = action.payload.text;
},
},
});
store.ts
:
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { useDispatch } from 'react-redux';
import { registrationFormSlice } from './registration';
const rootReducer = combineReducers({
registrationForm: registrationFormSlice.reducer,
});
export const setupStore = () => {
return configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production',
});
};
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore['dispatch'];
export const useAppDispatch: () => AppDispatch = useDispatch;
useFields.ts
:
import { useAppDispatch } from './store';
import { registrationFormSlice } from './registration';
export interface IgetFields {
label: string;
valueKey: string;
setValue: any;
disabled?: boolean;
}
export const useFields = (): (() => IgetFields[][]) => {
const dispatch = useAppDispatch();
const { setEmail } = registrationFormSlice.actions;
const getFields = (): IgetFields[][] => {
return [
[
{
label: 'Email',
valueKey: 'email',
setValue: (value: string) => dispatch(setEmail({ value, error: false, text: '' })),
},
],
];
};
return getFields;
};
useFields.test.tsx
:
import { renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { Provider } from 'react-redux';
import { setupStore } from './store';
import { useFields } from './useFields';
describe('useFields', () => {
const store = setupStore();
const setup = () => {
const { result } = renderHook(useFields, {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
return result.current();
};
it('Must be an array of arrays', () => {
const getFields = setup();
expect(Array.isArray(getFields)).toBeTruthy();
});
it('An array with index 0 must have certain properties and structure', () => {
const getFields = setup()[0];
expect(getFields).toHaveLength(1);
expect(getFields[0]).toEqual(
expect.objectContaining({
label: expect.any(String),
valueKey: expect.any(String),
setValue: expect.any(Function),
})
);
});
test('should set email value correctly', () => {
expect.assertions(1);
const getFields = setup()[0];
const firstField = getFields[0];
store.subscribe(() => {
expect(store.getState().registrationForm.data).toEqual({
email: {
value: 'test@example.com',
error: false,
errorText: '',
},
});
});
firstField.setValue('test@example.com');
});
});
Test result:
PASS stackoverflow/76031614/useFields.test.tsx (7.997 s)
useFields
✓ Must be an array of arrays (14 ms)
✓ An array with index 0 must have certain properties and structure (3 ms)
✓ should set email value correctly (4 ms)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
registration.ts | 100 | 100 | 100 | 100 |
store.ts | 100 | 100 | 100 | 100 |
useFields.ts | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 8.462 s, estimated 10 s
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论