在 React 中使用 preloadedState 进行测试:在函数中手动导入存储时,redux 存储不同步

Posted

技术标签:

【中文标题】在 React 中使用 preloadedState 进行测试:在函数中手动导入存储时,redux 存储不同步【英文标题】:Testing in React with preloadedState: redux store gets out of sync when having a manual import of store in a function 【发布时间】:2021-06-28 08:15:01 【问题描述】:

我正在使用 react-testing-library 和 jest 为 React 编写测试,当另一个文件导入存储时,我在弄清楚如何为我的 redux 存储设置 preloadedState 时遇到了一些问题。

我有这样设置我的商店的功能

store.ts

  export const history = createBrowserHistory()
  export const makeStore = (initalState?: any) => configureStore(
    reducer: createRootReducer(history),
    preloadedState: initalState
  )
  export const store = makeStore()

还有一个这样的js文件

utils.ts

  import store from 'state/store'
  const userIsDefined = () => 
    const state = store.getState()
    if (state.user === undefined) return false
    ...
    return true
  

然后我有一个看起来像这样的测试:

utils.test.tsx(renderWithProvider 基本上是一个渲染函数,它也将我的组件包装在一个 Render 组件中,请参阅:https://redux.js.org/recipes/writing-tests#connected-components)

  describe("Test", () => 
    it("Runs the function when user is defined", async () => 
      const store = makeStore( user:  id_token: '1'  )
      const  container  = renderWithProvider(
        <SomeComponent></SomeComponent>,
        store
      );
    )
  )

依次调用 utils.ts

中的函数

SomeComponent.tsx

  const SomeComponent = () => 
    if (userIsDefined() === false) return (<Forbidden/>)
    ...
  

现在..运行测试时发生的情况似乎是这样的。

    utils.ts 被读取并读取 import store from 'state/store' 行,这将创建并保存尚未定义用户的 store 变量。李> utils.test.tsx 被调用并运行调用 const store = makeStore( user: id_token: '1' ) 的代码。 renderWithProvider() 渲染 SomeComponent 进而调用 userIsDefined 函数。 if (state.user === undefined) 返回 false,因为 state.user 仍然未定义,我认为这是因为 utils.ts 已经像我调用自己的 ma​​keStore( user: id_token: '1' )? 之前一样导入了商店

我想要的答案: 我想确保当再次调用 makeStore() 时,它会更新 utils.ts 中使用的先前导入的商店版本。有没有办法做到这一点而不必使用 useSelector() 并将用户值从组件传递给我的 utils 函数?

例如,我可以做这样的事情,但我宁愿不这样做,因为我有很多这些文件和函数,并且非常依赖 import store from 'state/store'

SomeComponent.tsx

  const SomeComponent = () => 
    const user = useSelector((state: IState) => state.user)
    if (userIsDefined(user) === false) return (<Forbidden/>)
    ...
  

utils.ts

  //import store from 'state/store'
  const userIsDefined = (user) => 
    //const state = store.getState()
    if (user === undefined) return false
    ...
    return true
  

正如我上面所说,我不想这样做。


(顺便说一句,我似乎无法为此创建一个小提琴,因为我不知道如何为测试和这个用例做这个)

感谢您的任何帮助或推动正确方向。

【问题讨论】:

您如何以及在哪里调用这些实用程序?一般来说:是的,这就是为什么您通常不应该将您的状态随机导入其他文件的原因。您可以开玩笑地模拟完整的商店文件,但这很痛苦。 嗯,utils 文件被许多其他文件使用,是的,我不想模拟整个商店。也许有一种方法可以更新商店的状态,而不必再次调用 makeStore ......?不过还没有弄明白。 它们是如何使用的?仅在组件中?别的地方?举几个例子。您可能不得不在某处重构您的一些代码,因为现在您已经创建了具有“全局变量依赖关系”的不可测试代码。 一般情况下:您应该永远调用从组件侧面导入商店的方法。状态更新时组件不会改变。本质上,您现在只是在发现应用程序中的错误。 【参考方案1】:

这只是发现了您的应用程序中的一个错误:您在反应组件中使用了对store 的直接访问,这是您应该永远做的事情。当该状态更改并不同步时,您的组件将不会重新呈现。

如果你真的想要一个这样的助手,请用它制作一个自定义钩子:

  import store from 'state/store'
  const useUserIsDefined = () => 
    const user = useSelector(state => state.user)
    if (user === undefined) return false
    ...
    return true
  

这样您的助手不需要直接访问store,并且当该存储值更改时组件将正确重新呈现。

【讨论】:

啊,谢谢,我可能会考虑用它制作一个自定义挂钩 如果您想改善重新渲染行为,请在选择器中进行测试:const useUserIsDefined = () =&gt; useSelector(state =&gt; state.user !== undefined ) 只有当值从 true 变为 false 或其他方式而不是每次时间state.user 变化。 使用您所描述的自定义钩子似乎效果很好。即使我再次调用 makeStore() ,它也会检测到更改并使我的测试通过。谢谢

以上是关于在 React 中使用 preloadedState 进行测试:在函数中手动导入存储时,redux 存储不同步的主要内容,如果未能解决你的问题,请参考以下文章

react系列在React中使用Redux

是否可以在 React 中使用 Polymer?

无法在 React Native 应用程序中使用 React.memo

React-Redux:在 React 组件中使用动作创建器

[react] 在React中怎么使用字体图标?

[react] 举例说明在react中怎么使用样式