- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我是 redux 工具包库的新手,尤其是在测试方面。我浏览了文档并阅读了一堆关于这个主题的帖子和文章,但仍然很挣扎。我构建了一个简单的待办事项应用程序,并包含了几个 API 请求来涵盖异步情况。不过,测试这些结果有点挑战性。我希望就我的代码以及可以改进的地方获得一些建议和反馈。我还想对测试 createAsyncThunk 切片是否有意义提出一些意见。注意:我对测试 API 调用本身不感兴趣,并使用模拟数据重新创建成功的请求。
build 性的批评非常有帮助,将不胜感激
请看一下我的切片文件并进行测试
postSlice.ts
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { RootState } from "../../store";
import axios from "axios";
export type Post = {
userId: number;
id: number;
title: string;
body: string;
};
export type PostsState = {
posts: Post[];
loading: boolean;
error: null | string;
};
export const initalPostState: PostsState = {
posts: [],
loading: false,
error: null,
};
export const fetchAllPosts = createAsyncThunk(
"posts/allPosts",
async (data, { rejectWithValue }) => {
try {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/posts`
);
return (await response.data) as Post[];
} catch (err) {
if (!err.response) {
throw err;
}
return rejectWithValue(err.response.data);
}
}
);
export const fetchSuccessful = fetchAllPosts.fulfilled;
export const fetchPending = fetchAllPosts.pending;
export const fetchFailed = fetchAllPosts.rejected;
const postsSlice = createSlice({
name: "Posts",
initialState: initalPostState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchSuccessful, (state, { payload }) => {
state.posts = payload;
state.loading = false;
});
builder.addCase(fetchPending, (state, action) => {
state.loading = true;
});
builder.addCase(fetchFailed, (state, action) => {
state.error = action.error.message
? action.error.message
: "Failed to load data";
state.loading = false;
});
},
});
export const selectPosts = (state: RootState) => state.fetchedPosts;
export const fetchedPostsReducer = postsSlice.reducer;
import {
initalPostState,
fetchPending,
fetchFailed,
selectPosts,
fetchSuccessful,
fetchedPostsReducer,
} from "./postsSlice";
import { Post, PostsState } from "./postsSlice";
import store, { RootState } from "../../store";
const appState = store.getState();
describe("postsSlice", () => {
describe("Posts State, Posts Action and Selector", () => {
it("should set loading state on true when API call is pending", async (done) => {
// Arrange
// Act
const nextState: PostsState = await fetchedPostsReducer(
initalPostState,
fetchPending
);
// Assert
const rootState: RootState = { ...appState, fetchedPosts: nextState };
expect(selectPosts(rootState).loading).toBeTruthy();
expect(selectPosts(rootState).error).toBeNull();
done();
});
it("should set error state when API call is rejected", async (done) => {
// Arrange
const response = {
message: "Network request failed",
name: "error",
};
// Act
const nextState: PostsState = await fetchedPostsReducer(
initalPostState,
fetchFailed(response, "")
);
// Assert
const rootState: RootState = { ...appState, fetchedPosts: nextState };
expect(selectPosts(rootState).loading).toBeFalsy();
expect(selectPosts(rootState).error).not.toBeNull();
expect(selectPosts(rootState).error).toEqual("Network request failed");
done();
});
it("should update state when API call is successful", async (done) => {
// Arrange
const response: Post[] = [
{
userId: 1,
id: 1,
title:
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
body:
"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
},
{
userId: 1,
id: 2,
title: "qui est esse",
body:
"est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla",
},
];
// Act
const nextState: PostsState = await fetchedPostsReducer(
initalPostState,
fetchSuccessful(response, "")
);
// Assert
const rootState: RootState = { ...appState, fetchedPosts: nextState };
expect(selectPosts(rootState).loading).toBeFalsy();
expect(selectPosts(rootState).error).toBeNull();
expect(selectPosts(rootState).posts).toEqual(
expect.arrayContaining(response)
);
done();
});
});
});
最佳答案
我已经在 GitHub 上回答了 redux 工具包的问题,但我也会在这里发帖,因为这是我在尝试自己的解决方案之前访问的众多链接之一。
解释
由于createAsyncThunk
返回一个函数供以后执行,您可以利用它来发挥自己的优势。无需麻烦地测试整个商店与您的 thunk 的交互,您可以在远离商店的情况下单独测试 thunk 本身。
运行您的jest.mock
调用模拟您可能用来访问服务器或本地状态的任何 API/ Hook ,更改这些解析/返回的内容,然后执行您保存的方法。这样做可以让您访问 createAsyncThunk
中的 promise /方法。使用您通常会调用的参数进行调用。
具体问题
您不想测试商店,而是要测试 thunk 是否正在调度商店所依赖的操作来设置诸如加载、您要保存的错误消息等。这样您就可以为您的 reducer 创建测试,其中您可以在每次测试时重新创建一个全新的商店,并确保通过这些 reducer 进行的所有转换都是正确的。
通克
// features/account/thunks.ts
import api from './api'; // http calls to the API
import { actions } from './reducer'; // "actions" from a createSlice result
import { useRefreshToken } from './hooks'; // a `useSelector(a => a.account).auth?.refreshToken` result
// declare and code as normal
export const register = createAsyncThunk(
'accounts/register',
async (arg: IRegisterProps, { dispatch }) => {
try {
const data = await api.register(arg);
dispatch(actions.authSuccess(data));
} catch (err) {
console.error('Unable to register', err);
}
}
);
// Using a hook to access state
export const refreshSession = createAsyncThunk(
'accounts/refreshSession',
async (_, { dispatch }) => {
// or add `, getState` beside dispatch and do token = getState().accounts.auth.refreshToken;
// If you use getState, your test will be more verbose though
const token: string = useRefreshToken();
try {
const data = await api.refreshToken(token);
dispatch(actions.tokenRefreshed(data));
} catch (err) {
console.error('Unable to refresh token', err);
}
}
);
考试
// features/account/thunks.test.ts
import apiModule from './api';
import hookModule from './hooks';
import thunks from './thunks';
import { actions } from './reducer';
import { IRegisterProps } from './types';
import { AsyncThunkAction, Dispatch } from '@reduxjs/toolkit';
import { IAuthSuccess } from 'types/auth';
jest.mock('./api');
jest.mock('./hooks')
describe('Account Thunks', () => {
let api: jest.Mocked<typeof apiModule>;
let hooks: jest.Mocked<typeof hookModule>
beforeAll(() => {
api = apiModule as any;
hooks = hookModule as any;
});
// Clean up after yourself.
// Do you want bugs? Because that's how you get bugs.
afterAll(() => {
jest.unmock('./api');
jest.unmock('./hooks');
});
describe('register', () => {
// We're going to be using the same argument, so we're defining it here
// The 3 types are <What's Returned, Argument, Thunk Config>
let action: AsyncThunkAction<void, IRegisterProps, {}>;
let dispatch: Dispatch; // Create the "spy" properties
let getState: () => unknown;
let arg: IRegisterProps;
let result: IAuthSuccess;
beforeEach(() => {
// initialize new spies
dispatch = jest.fn();
getState = jest.fn();
api.register.mockClear();
api.register.mockResolvedValue(result);
arg = { email: 'me@myemail.com', password: 'yeetmageet123' };
result = { accessToken: 'access token', refreshToken: 'refresh token' };
action = thunks.registerNewAccount(arg);
});
// Test that our thunk is calling the API using the arguments we expect
it('calls the api correctly', async () => {
await action(dispatch, getState, undefined);
expect(api.register).toHaveBeenCalledWith(arg);
});
// Confirm that a success dispatches an action that we anticipate
it('triggers auth success', async () => {
const call = actions.authSuccess(result);
await action(dispatch, getState, undefined);
expect(dispatch).toHaveBeenCalledWith(call);
});
});
describe('refreshSession', () => {
// We're going to be using the same argument, so we're defining it here
// The 3 types are <What's Returned, Argument, Thunk Config>
let action: AsyncThunkAction<void, unknown, {}>;
let dispatch: Dispatch; // Create the "spy" properties
let getState: () => unknown;
let result: IAuthSuccess;
let existingToken: string;
beforeEach(() => {
// initialize new spies
dispatch = jest.fn();
getState = jest.fn();
existingToken = 'access-token-1';
hooks.useRefreshToken.mockReturnValue(existingToken);
api.refreshToken.mockClear();
api.refreshToken.mockResolvedValue(result);
result = { accessToken: 'access token', refreshToken: 'refresh token 2' };
action = thunks.refreshSession();
});
it('does not call the api if the access token is falsy', async () => {
hooks.useRefreshToken.mockReturnValue(undefined);
await action(dispatch, getState, undefined);
expect(api.refreshToken).not.toHaveBeenCalled();
});
it('uses a hook to access the token', async () => {
await action(dispatch, getState, undefined);
expect(hooks.useRefreshToken).toHaveBeenCalled();
});
// Test that our thunk is calling the API using the arguments we expect
it('calls the api correctly', async () => {
await action(dispatch, getState, undefined);
expect(api.refreshToken).toHaveBeenCalledWith(existingToken);
});
// Confirm a successful action that we anticipate has been dispatched too
it('triggers auth success', async () => {
const call = actions.tokenRefreshed(result);
await action(dispatch, getState, undefined);
expect(dispatch).toHaveBeenCalledWith(call);
});
});
});
关于测试 createAsyncThunk Redux Toolkit Jest,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62253049/
Redux Toolkit 在尝试更新嵌套数组上的状态时给了我突变错误,我认为它是使用 immer 来解决这个问题并简化 reducer。 我的商店看起来像: 状态 -> 表单 -> 部分 我想向现有
DragonRuby Game Toolkit 中好像没有按钮的概念。如何创建按钮等 UI 组件? 最佳答案 按钮(和任何其他 UI 组件)可以解构为 primitives: 按钮有一个点击空间(通常
我正在使用 DragonRuby Game Toolkit 构建游戏。 如何检测一个对象(例如 Sprite)是否与另一个 Sprite 发生碰撞? 这是放置在屏幕上的两个 Sprite 。关于如何检
我正在通过 Template Toolkit 文件为 Template Toolkit 制作一些文档。目标是显示我正在使用的代码以及代码的输出。现在,我正在复制代码并将所有“%”字符替换为“%”字符串
我真的很喜欢 Template Toolkit并且喜欢它如何与 Catalyst 一起使用,但我想要更多“网络高级”工具包。 它可能只是 Web 对象的 *.tt 文件包,例如:Selector、Se
我需要为我正在处理的应用程序使用 Surface 项目模板,但我也想使用 MVVM Light Toolkit。我发现我可以“添加 | 新项目...”并为 View 、 View 模型或定位器选择一个
我们一直在使用 google identity toolkit 在我们的 alpha 和 beta 网站上登录,它非常流畅、简单且易于操作(Alpha 和 beta 用户获得了特定的登录说明)。当我们
已关闭。此问题旨在寻求有关书籍、工具、软件库等的建议。不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以
我想为 OpenCV 安装 CUDA,但当前的工具包 (7.5) 与 Visual Studio 2015 不兼容。我的问题是 - 安装 VS 2013 Express 是否允许我在 2015 年使用
我在添加 时遇到上述错误extraReducer 到我的 创建切片。 这是一个 react 原生应用程序 这是我的代码: export const login = createAsyncThu
使用 RTK Query , 如何使用获取功能来更新另一个切片的状态? 本质上,我试图让所有相关状态彼此相邻,因此在使用 useLazyGetQuery 查询数据后,我想使用结果并将其存储在现有切片中
我想使用模板工具包获取一个随机数。它不必特别随机。我该怎么做? 最佳答案 嗯,如果您没有(或无法导入)Slash::Test,您可能会遇到问题。 从 TT 的“vanilla”安装,您可以简单地使用
我的计算机上插入了两个屏幕,想知道 JFrame 或 Toolkit 中是否有检测窗口在哪个屏幕上的方法? 我有这个代码: java.awt.Toolkit.getDefaultToolkit().g
我正在使用 java 获取屏幕的尺寸和分辨率。当我运行以下代码时,我得到以下输出。 Toolkit toolkit = Toolkit.getDefaultToolkit (); Dimension
import { createSlice } from '@reduxjs/toolkit' export const countersSlice = createSlice({ name:
我有一个 azure 函数,我想在 IntelliJ IDEA 中本地运行和测试。我按照此处列出的所有步骤进行操作:https://learn.microsoft.com/en-us/azure/az
使用 VBA 我想将当前 word 文档的副本发送到 Web 服务?如何将当前文档作为字节数组获取? 我知道如何使用网络服务只是不确定如何将当前文件作为二进制对象发送? 附言从今天早上开始我一直在使用
如果要通过插件系统添加函数和/或虚拟方法,我想将自己的指令添加到Template Toolkit中。在不深入Template::Grammar的情况下,是否容易做到这一点?还有什么我可以在CPAN上学
如何使用 RTK 将整个数组分配给我的 intialState 对象? 做state = payload或 state = [...state, ...payload]不更新任何东西。 例子: con
我正在为我的网站使用谷歌翻译器。我想隐藏谷歌翻译器的顶部栏让我知道如何隐藏那个? 请在此处查看我的网站链接 http://www.rewords.com让我知道我要隐藏那个酒吧? 谢谢 最佳答案 通过
我是一名优秀的程序员,十分优秀!