在 React 中测试 Actions、Reducers 和 Contexts
Testing Actions, Reducers, & Contexts in React
我已经使用 Hooks 和 Context 构建了多个 React 功能组件。一切正常。现在我需要为所有内容编写测试。我对如何与他们中的一些人一起前进感到困惑,所以想接触社区。
操作数
这是我的一个 Actions 文件的示例:
export const ADD_VEHICLE: 'ADD_VEHICLE' = 'ADD_VEHICLE';
export const UPDATE_VEHICLE: 'UPDATE_VEHICLE' = 'UPDATE_VEHICLE';
type AddVehicleAction = {type: typeof ADD_VEHICLE, isDirty: boolean};
type UpdateVehicleAction = {type: typeof UPDATE_VEHICLE, id: number, propName: string, payload: string | number};
export type VehiclesActions =
| AddVehicleAction
| UpdateVehicleAction;
我该如何测试这个 Actions 文件?我的意思不是与其他任何东西结合,我的意思是它并且只有它?
从评论来看,我似乎同意此文件中没有可直接测试的内容。
减速机
我的每个 Reducers 文件都直接连接到并支持特定的 Context。这是我的一个 Reducers 文件的示例:
import type { VehiclesState } from '../VehiclesContext';
import type { VehiclesActions } from '../actions/Vehicles';
import type { Vehicle } from '../SharedTypes';
import { ADD_VEHICLE,
UPDATE_VEHICLE
} from '../actions/Vehicles';
export const vehiclesReducer = (state: VehiclesState, action: VehiclesActions) => {
switch (action.type) {
case ADD_VEHICLE: {
const length = state.vehicles.length;
const newId = (length === 0) ? 0 : state.vehicles[length - 1].id + 1;
const newVehicle = {
id: newId,
vin: '',
license: ''
};
return {
...state,
vehicles: [...state.vehicles, newVehicle],
isDirty: action.isDirty
};
}
case UPDATE_VEHICLE: {
return {
...state,
vehicles: state.vehicles.map((vehicle: Vehicle) => {
if (vehicle.id === action.id) {
return {
...vehicle,
[action.propName]: action.payload
};
} else {
return vehicle;
}
}),
isDirty: true
};
}
如果您只想为这个 Reducers 文件构建测试,您会使用什么方法?我的想法是像这样渲染 DOM:
function CustomComponent() {
const vehiclesState = useVehiclesState();
const { isDirty,
companyId,
vehicles
} = vehiclesState;
const dispatch = useVehiclesDispatch();
return null;
}
function renderDom() {
return {
...render(
<VehiclesProvider>
<CustomComponent />
</VehiclesProvider>
)
};
}
虽然上面的代码确实 运行,但我现在遇到的问题是 vehiclesState
和 dispatch
在我的测试代码中都无法访问,所以我想弄清楚如何到 "surface" 每个 describe / it
构造中的那些。如有任何建议,我们将不胜感激。
上下文
我的上下文遵循 Kent C. Dodds 概述的相同模式:https://kentcdodds.com/blog/how-to-use-react-context-effectively - 因为 StateContext 和 DispatchContext 是分开的,并且有一个默认状态。考虑到此代码模式并考虑到我已经为 Context 的 Reducers 准备了一个单独的测试文件,具体可以针对 ONLY 进行哪些测试?
和我的评论一样,我真的认为你应该阅读 redux docs for writing tests 这样你就可以大致了解该怎么做。
但是因为你已经有一个减速器,你想编写你的测试用例来遵循这个模式
- 每个动作至少有 1 个测试
- 每个测试都会有一个 "previous state",它会被改变
- 你将调用你的减速器,传递动作和之前的状态
- 您将断言您的新状态与预期相同
这是一个代码示例:
it('adds a new car when there are no cars yet', () => {
// you want to put here values that WILL change, so that you don't risk
// a false positive in your unit test
const previousState = {
vehicles: [],
isDirty: false,
};
const state = reducer(previousState, { type: ADD_VEHICLE });
expect(state).toEqual({
vehicles: [{
id: 1,
vin: '',
license: '',
}],
isDirty: true,
});
});
it('adds a new car when there are existing cars already, () => {
// ...
});
我还建议使用动作创建器而不是直接创建动作对象,因为它更具可读性:
// actions.js
export const addVehicle = () => ({
type: ADD_VEHICLE
})
// reducer.test.js
it('adds a new car when there are no cars yet', () => {
//...
const state = reducer(previousState, actions.addVehicle());
我已经使用 Hooks 和 Context 构建了多个 React 功能组件。一切正常。现在我需要为所有内容编写测试。我对如何与他们中的一些人一起前进感到困惑,所以想接触社区。
操作数 这是我的一个 Actions 文件的示例:
export const ADD_VEHICLE: 'ADD_VEHICLE' = 'ADD_VEHICLE';
export const UPDATE_VEHICLE: 'UPDATE_VEHICLE' = 'UPDATE_VEHICLE';
type AddVehicleAction = {type: typeof ADD_VEHICLE, isDirty: boolean};
type UpdateVehicleAction = {type: typeof UPDATE_VEHICLE, id: number, propName: string, payload: string | number};
export type VehiclesActions =
| AddVehicleAction
| UpdateVehicleAction;
我该如何测试这个 Actions 文件?我的意思不是与其他任何东西结合,我的意思是它并且只有它? 从评论来看,我似乎同意此文件中没有可直接测试的内容。
减速机 我的每个 Reducers 文件都直接连接到并支持特定的 Context。这是我的一个 Reducers 文件的示例:
import type { VehiclesState } from '../VehiclesContext';
import type { VehiclesActions } from '../actions/Vehicles';
import type { Vehicle } from '../SharedTypes';
import { ADD_VEHICLE,
UPDATE_VEHICLE
} from '../actions/Vehicles';
export const vehiclesReducer = (state: VehiclesState, action: VehiclesActions) => {
switch (action.type) {
case ADD_VEHICLE: {
const length = state.vehicles.length;
const newId = (length === 0) ? 0 : state.vehicles[length - 1].id + 1;
const newVehicle = {
id: newId,
vin: '',
license: ''
};
return {
...state,
vehicles: [...state.vehicles, newVehicle],
isDirty: action.isDirty
};
}
case UPDATE_VEHICLE: {
return {
...state,
vehicles: state.vehicles.map((vehicle: Vehicle) => {
if (vehicle.id === action.id) {
return {
...vehicle,
[action.propName]: action.payload
};
} else {
return vehicle;
}
}),
isDirty: true
};
}
如果您只想为这个 Reducers 文件构建测试,您会使用什么方法?我的想法是像这样渲染 DOM:
function CustomComponent() {
const vehiclesState = useVehiclesState();
const { isDirty,
companyId,
vehicles
} = vehiclesState;
const dispatch = useVehiclesDispatch();
return null;
}
function renderDom() {
return {
...render(
<VehiclesProvider>
<CustomComponent />
</VehiclesProvider>
)
};
}
虽然上面的代码确实 运行,但我现在遇到的问题是 vehiclesState
和 dispatch
在我的测试代码中都无法访问,所以我想弄清楚如何到 "surface" 每个 describe / it
构造中的那些。如有任何建议,我们将不胜感激。
上下文 我的上下文遵循 Kent C. Dodds 概述的相同模式:https://kentcdodds.com/blog/how-to-use-react-context-effectively - 因为 StateContext 和 DispatchContext 是分开的,并且有一个默认状态。考虑到此代码模式并考虑到我已经为 Context 的 Reducers 准备了一个单独的测试文件,具体可以针对 ONLY 进行哪些测试?
和我的评论一样,我真的认为你应该阅读 redux docs for writing tests 这样你就可以大致了解该怎么做。
但是因为你已经有一个减速器,你想编写你的测试用例来遵循这个模式
- 每个动作至少有 1 个测试
- 每个测试都会有一个 "previous state",它会被改变
- 你将调用你的减速器,传递动作和之前的状态
- 您将断言您的新状态与预期相同
这是一个代码示例:
it('adds a new car when there are no cars yet', () => {
// you want to put here values that WILL change, so that you don't risk
// a false positive in your unit test
const previousState = {
vehicles: [],
isDirty: false,
};
const state = reducer(previousState, { type: ADD_VEHICLE });
expect(state).toEqual({
vehicles: [{
id: 1,
vin: '',
license: '',
}],
isDirty: true,
});
});
it('adds a new car when there are existing cars already, () => {
// ...
});
我还建议使用动作创建器而不是直接创建动作对象,因为它更具可读性:
// actions.js
export const addVehicle = () => ({
type: ADD_VEHICLE
})
// reducer.test.js
it('adds a new car when there are no cars yet', () => {
//...
const state = reducer(previousState, actions.addVehicle());