【问题标题】:Proper way to refactor functions that use functions within the same file for testing重构使用同一文件中的函数进行测试的函数的正确方法
【发布时间】:2019-09-06 02:29:45
【问题描述】:

在我们的应用程序中,我们需要根据当前用户是否经过身份验证来使用不同的端点。身份验证的状态存储在 Redux 状态中。这是我使用 redux 选择器的常见模式:

// selectors.js

import {selectIsAuthenticated} from 'Redux/customer/selectors'

export const selectEndpoint = state => selectIsAuthenticated(state) ? 'guest' : 'authenticated'

export const selectEndpointA = state => `/rest/${selectEndpoint(state)}/a`

export const selectEndpointB = state => `/rest/${selectEndpoint(state)}/b`

测试时,这变得复杂,因为我无法模拟 selectEndpoint.. 所以 selectEndpointA 的测试变得依赖于 selectEndpoint 的实现,这并不理想。

什么是测试这样的东西的正确方法或重构它以更容易测试?

我考虑过重构,以便将 selectEndpoint 作为selectEndpointA 的参数(或咖喱)传入,例如:

export const selectEndpointA = selectEndpoint => state => `/rest/${selectEndpoint(state)}/a`

但这似乎过于复杂,因为现在在我的代码中,我需要在任何我想使用的地方添加导入 selectEndpointA

编辑:

这是我的单元测试示例:

import * as selectors from 'Redux/global/selectors'
import * as customerSelectors from 'Redux/customer/selectors'
const nonAuthenticatedEndpoint = 'guest'
/* ... */
  describe('selectEndpoint', () => {
    it('returns proper endpoint for non authenticated user', () => {
      customerSelectors.selectIsAuthenticated = jest.fn(() => false)
      const state = 'state'
      const expected = nonAuthenticatedEndpoint
      expect(selectors.selectEndpoint(state)).toEqual(expected)
      expect(customerSelectors.selectIsAuthenticated).toHaveBeenCalledWith(
        state
      )
    })
}

所以Redux/global/selectors 内还有selectEndpointAselectEndpointB

编辑:

还有一个我想做的测试示例:

  describe('selectEndpointA', () => {
    it('returns correct endpoint for non authenticated user', () => {
      const state = {
          /* some specific state */
      }
      expect(selectors.selectEndpointA(state)).toEqual('/rest/guest/a')
    })
  })

虽然目前这确实有效,但我想找到一种方法将其与 selectEndpoint 的实现分离,并且比较值不依赖于 selectEndpoint 的返回

【问题讨论】:

  • 如果你正在导出selectEndpoint,你可以在你的测试中存根它,以便它返回你想要的。您能在测试文件中展示您尝试过的内容吗?
  • @mgarcia 我确实为我的反应组件导出它,但我想直接测试我的选择器,主要是为了能够确定更改是否会破坏任何功能
  • @mgarcia 我的理解是 jest mocks 只适用于导入,所以因为这两个函数在同一个文件中,jest 没有办法创建一个 mock

标签: javascript unit-testing testing redux jestjs


【解决方案1】:

您询问的是函数模拟,特别是 jest.mockmockReturnValue。模拟导入并定义您的函数应返回的内容。

import * as selectors from 'Redux/global/selectors'
jest.mock('Redux/global/selectors')

selectors.selectEndpointA.mockReturnValue(() => 'whatever you want')

【讨论】:

  • 所以这会破坏我所有其他测试,对于我在这个测试文件中拥有的Redux/global/selectors 下的函数
  • 我想我可以针对正在测试该功能的特定测试将每个功能重新调整为真正的功能,但这似乎是朝着错误方向迈出的一步……就测试冗长/耦合而言
  • @Paul 将您的选择器测试拆分为不同的单独文件。每个文件都应该有自己的测试文件。如果这个一个与单个文件绑定的测试文件,那意味着单个文件做的太多了,应该拆分成更小的文件
【解决方案2】:

好吧,我最终解决这个问题的方法是结合使用柯里化和默认值:

重构了我的端点选择函数如下:

// selectors.js

import {selectIsAuthenticated} from 'Redux/customer/selectors'

export const selectEndpoint = state => selectIsAuthenticated(state) ? 'guest' : 'authenticated'

export const selectEndpointA = (endpointSelector = selectEndpoint) => state => `/rest/${endpointSelector(state)}/a`

export const selectEndpointB = (endpointSelector = selectEndpoint) => state => `/rest/${endpointSelector(state)}/b`

以及由此产生的测试...

  describe('selectEndpointA', () => {
    it('returns correct endpoint', () => {
      const test = 'test'
      const state = 'state'
      const selectEndpoint = jest.fn(() => test)
      expect(selectors.selectEndpointA(selectEndpoint)(state)).toEqual(`/rest/${test}/a`)
      expect(selectEndpoint).toHaveBeenCalledWith(state)
    })
  })

然后因为有一个默认值,就意味着在我其他地方的使用中我可以使用:

const endpointA = selectEndpointA()(state)

不关心是什么函数决定了端点是如何确定的,也不关心该函数返回什么(但可以单独测试)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-25
    相关资源
    最近更新 更多