gpt4 book ai didi

testing - 我正在努力测试这个 redux saga

转载 作者:行者123 更新时间:2023-11-28 21:38:08 28 4
gpt4 key购买 nike

我是 redux-saga 的新手,很难测试这段代码:

import { normalize } from 'normalizr';

export function* normalizeResponse(denormalized, schema) {
const normalized = yield call(normalize, denormalized, schema);
return normalized;
}

export function* request(apiFn, action, schema) {
try {
yield put(requestStart({ type: action.type }));
const denormalized = yield call(apiFn, action.payload, action.meta);
const normalized = yield call(normalizeResponse, denormalized, schema);
yield put(requestSuccess({ type: action.type }));
return normalized;
} catch (e) {
if (__DEV__ && !__TEST__) {
Alert.alert('Something went wrong');
console.log(`Error in request saga: ${action.type}`, e);
}
if (action.type) {
const payload = { type: action.type, error: e };
const meta = action.payload || {};
yield put(requestFailure(payload, meta));
}
}
}

export function* photosShow() {
while (true) {
const action = yield take(t.PHOTOS_SHOW);
const normalized = yield call(request, api.show, action, {
photo: schema.photo,
});
if (normalized) yield put(setEntities(normalized));
}
}

在网上,我找到了许多 redux saga 测试包和一些教程,但它们似乎都涵盖了基础知识。以下是 saga 工作原理的分步说明:

  • photosShow 使用 Flux 标准操作调用,负载为 { id: 1}
  • 这将调用生成器 request,这是一个实用函数,用于发出 API 请求,然后规范化响应。
  • 首先,一个requestStart Action 会被触发
  • 然后将调用 api 端点
  • 如果成功,将触发 requestSuccess 操作
  • 然后将使用 normalizr 对响应进行规范化
  • 然后使用 setEntities 存储在 redux 状态(返回 photosShow)

我们将不胜感激任何有关如何实现这一目标的帮助。

最佳答案

我也在努力阅读所有的 redux-saga 测试资源...没有找到合适的解决方案(至少对我而言)。

我最终得到的是:

  • 我“渲染”了一个带有工作商店的空 React 应用
  • 我手动触发有趣的 Action (触发我正在测试的 sagas 的 Action )
  • 我监视 sagas 消耗的所有外部资源

最后:我触发了 sagas,我发现它们触发了其他东西。

我将 sagas 视为黑盒,并检查它们是否遵守与应用程序所有其他部分的契约。

我从我的身份验证 sagas 测试中举了一个例子(我打破了很多测试良好实践,我知道,它来 self 早期使用 saga 进行的测试)(参见下面的 renderWithReduxspyUtil 函数):

describe("Login flow with valid credentials", () => {
let user = "stefano";
let pwd = "my_super_secret_password";
let app;
let spies;
// const spiedConsole = spyConsole();
beforeAll(() => {
app = renderWithRedux(<></>);
spies = {
LOGIN_SUCCESS_creator: spyUtil(authActions, "LOGIN_SUCCESS_creator"),
navigate: spyUtil(ReachRouter, "navigate"),
postLogin: spyUtil(authNetwork, "postLogin", postLoginOk),
redirectBackFromLoginPage: spyUtil(navigationData, "redirectBackFromLoginPage")
};
});
test("1 - the login API should be called as soon as the LOGIN_REQUEST action is dispatched", async () => {
app.store.dispatch(authActions.LOGIN_REQUEST_creator(user, pwd));
expect(spies.postLogin.spy).toHaveBeenCalledWith(user, pwd);
});
test("2 - then when the login API is successfull, a LOGIN_SUCCESS action should be dispatched with the tokens", async () => {
expect(spies.LOGIN_SUCCESS_creator.spy).toHaveBeenCalledWith(
expect.any(String),
expect.any(String)
);
});
test("3 - then the router should be asked to make a redirect to the initial location", async () => {
expect(spies.redirectBackFromLoginPage.spy).toHaveBeenCalled();
expect(spies.navigate.spy).toHaveBeenCalledWith(expect.stringMatching(/\//));
});
afterAll(() => {
spies.values().forEach(obj => obj.spy.mockRestore());
// spiedConsole.mockRestore();
cleanup();
});
});

一步一步:- 我渲染了一个空的应用程序,其中包含一个可用的 Redux+Saga 商店

app = renderWithRedux(<></>);
  • 我窥探传奇之外的一切
spies = {
LOGIN_SUCCESS_creator: spyUtil(authActions, "LOGIN_SUCCESS_creator"),
navigate: spyUtil(ReachRouter, "navigate"),
postLogin: spyUtil(authNetwork, "postLogin", postLoginOk),
redirectBackFromLoginPage: spyUtil(navigationData, "redirectBackFromLoginPage")
};

哪里:

  • LOGIN_SUCCESS_creator 是 Action 创建者
  • navigate 来自 Reach Router
  • postLogin 发出 AJAX 请求(用一个假函数模拟它,几乎立即返回“成功”响应(但解决 promise ))
  • redirectBackFromLoginPage 是一个再次使用(在某些条件下)navigate 实用程序

    的函数
    • 我触发了 LOGIN_REQUEST 操作,我希望 AJAX 触发函数已使用正确的凭据调用
test("1 - the login API should be called as soon as the LOGIN_REQUEST action is dispatched", async () => {
app.store.dispatch(authActions.LOGIN_REQUEST_creator(user, pwd));
expect(spies.postLogin.spy).toHaveBeenCalledWith(user, pwd);
});
  • 我检查 LOGIN_SUCCESS 操作是否将与身份验证 token 一起分派(dispatch)
test("2 - then when the login API is successfull, a LOGIN_SUCCESS action should be dispatched with the tokens", async () => {
expect(spies.LOGIN_SUCCESS_creator.spy).toHaveBeenCalledWith(
expect.any(String),
expect.any(String)
);
});
  • 我检查是否使用正确的路由调用了路由器(/ 用于主页)
test("3 - then the router should be asked to make a redirect to the initial location", async () => {
expect(spies.redirectBackFromLoginPage.spy).toHaveBeenCalled();
expect(spies.navigate.spy).toHaveBeenCalledWith(expect.stringMatching(/\//));
});
  • 然后,我清除所有内容
afterAll(() => {
spies.values().forEach(obj => obj.spy.mockRestore());
// spiedConsole.mockRestore();
cleanup();
});




这是“我的”(它来自 Kent C. Dodds)renderWithRedux 函数

// @see https://github.com/kentcdodds/react-testing-library/blob/master/examples/__tests__/react-redux.js
export function renderWithRedux(ui, { initialState, store = configureStore() } = {}) {
return {
...render(
<div>
<Provider store={store}>{ui}</Provider>
</div>
),
// adding `store` to the returned utilities to allow us
// to reference it in our tests (just try to avoid using
// this to test implementation details).
store
};
}

configureStore 是我的一个函数,它使用各种中间件构建整个 Redux 存储。

这是我的spyUtil 函数

/**
* A all-in-one spy and mock function
* @param {object} obj
* @param {string} name
* @param {function} mockFunction
*/
export function spyUtil(obj, name, mockFunction = undefined) {
const spy = jest.spyOn(obj, name);
let mock;
if (mockFunction) {
mock = jest.fn(mockFunction);
obj[name].mockImplementation(mock);
}
return { spy, mock };
}

请注意,这只是其中一种身份验证流程,我并未在此处报告所有情况。

我想知道你对此的想法😉

关于testing - 我正在努力测试这个 redux saga,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55734309/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com