【问题标题】:How to test functions in component with enzyme, jest, recompose, react如何用酶、笑话、重组、反应测试组件中的功能
【发布时间】:2018-08-24 12:54:27
【问题描述】:

好的,所以我对如何使用酶/玩笑测试我的组件的功能有点困惑。仍在学习如何测试我的组件 - 我可以编写简单的测试,但现在我需要让它们变得更复杂。

我想知道测试我的组件的函数是否被正确调用以及它们是否按预期更新状态道具的最佳方法。我发现很棘手的是我的函数和状态都存在于我的组件的道具中。

如果我需要使用间谍,我希望知道如何使用 Jest,但如果像 Sinon 或 Jasmine 这样的部门更适合我的工作,我愿意接受(请告诉我原因,所以我可以更好地理解)。

例如,我有一个 UserDetails 组件

const UserDetails = ({
 userInfo,
 onUserInfoUpdate,
 className,
 error,
 title,
 primaryBtnTitle,
 submit,
 secondaryBtnTitle,
 secondaryBtnFunc,
 ...props
}) => (
 <div className={className}>
   <div className="user-details-body">
     <Section title="User details">
       <TextInput
         className="firstName"
         caption="First Name"
         value={userInfo.first}
         onChange={onUserInfoUpdate('first')}
         name="first-name"
         min="1"
         max="30"
         autoComplete="first-name"
       />
       <TextInput
         className="lastName"
         caption="Last Name"
         value={userInfo.last}
         onChange={onUserInfoUpdate('last')}
         name="last-name"
         min="1"
         max="30"
         autoComplete="last-name"
       />
     </Section>
   </div>

   <div className="errorBar">
     {error && <Alert type="danger">{error}</Alert>}
   </div>

   <ActionBar>
     <ButtonGroup>
       <Button type="secondary" onClick={secondaryBtnFunc}>
         {secondaryBtnTitle}
       </Button>
       <Button type="primary" onClick={submit}>
         {primaryBtnTitle}
       </Button>
     </ButtonGroup>
   </ActionBar>
 </div>  

TextInput 包括:

<label className={className}>
 {Boolean(caption) && <Caption>{caption}</Caption>}
 <div className="innerContainer">
   <input value={value} onChange={updateValue} type={type} {...rest} />     
 </div>
</label>

这是我的 index.js 文件的示例代码,该文件将我的 withState 和 withHandlers 组合到我的组件中:

import UserDetails from './UserDetails'
import { withState, withHandlers, compose } from 'recompose'

export default compose(
  withState('error', 'updateError', ''),
  withState('userInfo', 'updateUserInfo', {
    first: '',
    last: '',
  }),
  withHandlers({
    onUserInfoUpdate: ({ userInfo, updateUserInfo }) => key => e => {
      e.preventDefault()
      updateCardInfo({
        ...cardInfo,
        [key]: e.target.value,
      })
    },
    submit: ({ userInfo, submitUserInfo }) => key => e => {
      e.preventDefault()
      submitUserInfo(userInfo) 
      //submitUserInfo is a graphQL mutation
      })
    }  
  }) 
)

到目前为止,我的 test 文件如下所示:

import React from 'react'
import { mount } from 'enzyme' 
import UserDetails from './'
import BareUserDetails from './UserDetails'

describe('UserDetails handlers', () => {
  let tree, bareTree

  beforeEach(() => {
    tree = mount(
      <ThemeProvider theme={theme}>
        <UserDetails />
      </ThemeProvider>
    )
    bareTree = tree.find(BareUserDetails)
  })

  it('finds BareUserDetails props', () => {
    console.log(bareTree.props())
    console.log(bareTree.props().userInfo)
    console.log(bareTree.find('label.firstName').find('input').props())
  })

})

控制台日志向我返回了关于我在调用它们时期望看到的正确信息:

//console.log(bareTree.props())
   { error: '',
      updateError: [Function],
      userInfo: { first: '', last: '' },
      updateUserInfo: [Function],
      onUserInfoUpdate: [Function] }

//console.log(bareTree.props().userInfo)
   { first: '', last: '' }

//console.log(bareTree.find('label.firstName').find('input).props()
   { value: '',
      onChange: [Function],
      type: 'text',
      name: 'first-name',
      min: '1',
      max: '30',
      autoComplete: 'first-name' }

现在的问题是如何使用它们,以及最好的方法。我什至使用我的函数还是只是检查是否调用了 onChange?

更新(有点)

我已经尝试过了,我得到了以下结果:

  it('Input element updates userInfo with name onChange in FirstName input', () => {
    const firstNameInput = bareTree.find('label.firstName').find('input)
    ccNameInput.simulate('change', {target: {value: 'John'}})
    expect(ccNameInput.prop('onChange')).toHaveBeenCalled()
  })

在我的终端中,我得到:

 expect(jest.fn())[.not].toHaveBeenCalled()

    jest.fn() value must be a mock function or spy.
    Received:
      function: [Function anonymous]

但是,如果我尝试使用 Jest 创建一个 spyOn,则会收到一个错误,即它无法读取“未定义”的函数。

我试过spy = jest.spyOn(UserDetails.prototypes, 'onUpdateUserInfo')spy = jest.spyOn(BareUserDetails.prototypes, 'onUpdateUserInfo'),它们都抛出了错误。

【问题讨论】:

    标签: reactjs enzyme jestjs recompose


    【解决方案1】:

    我相信您应该分别测试哑组件 (UserDetails) 和 HOC。对于哑组件,您要使用 shallow 渲染组件并注入道具。要模拟 onUserInfoUpdate,您需要执行 const onUserInfoUpdate = jest.fn();

    你想要一些类似......的东西。

    import React from 'react'
    import { shallow } from 'enzyme' 
    import UserDetails from './UserDetails'
    
    const onUserInfoUpdate = jest.fn(); // spy
    const props = {
      onUserInfoUpdate,
      // list all your other props and assign them mock values
    };
    
    describe('UserDetails', () => {
      let tree;
    
      beforeAll(() => {
        tree = shallow(<UserDetails {...props} />)
      });
    
      it('should invoke the onUserInfoUpdate method', () => {
        const firstNameInput = tree.find('label.firstName').find('input');
        firstNameInput.simulate('change', { target: { value: 'John' } });
    
        expect(onUserInfoUpdate).toHaveBeenCalledWith('first');
      });
    });
    

    【讨论】:

    • 感谢您的回复!我已经尝试过了,到目前为止一切正常 - 唯一的问题是当我尝试使用附加到我的 TextInput 组件的 onChange 道具时。我可以将道具放在我的哑组件 UserDetails 中,但 TextInput 不会拾取这些道具,这是在输入中调用 onChange 所必需的。目前我收到一条未定义 onChange 的消息。
    • 当您执行tree.find('TextInput').length 时会发生什么?你应该得到 2。
    • 它的长度为 2(在我的 UserDetalis 组件中有两个 TextInputs)。当我检查道具时,onChange 是未定义的。
    • onChange 不是 AFAIK 的道具,而是一个事件。您要做的是在第一个 TextInput 上模拟更改事件。 tree.find('TextInput').simulate(change, , { target: { value: 'John' } })。只要确保你已经传递了所有的道具。
    • 你说得对,onChange 是一个事件。我之前没有提到的是,我使用了mount 而不是shallow,因为当我尝试在我的组件中找到TextInput 时,它的长度为0,即使我使用tree.dive().find('TextInput')。另外,我正在使用 styled-components,如果我尝试 console.log tree = shallow( &lt;ThemeProvider theme={theme}&gt; &lt;BareUserDetails {...props}/&gt; &lt;/ThemeProvider&gt; ) 的 html,我会收到 TypeError: Cannot read property 'backgroundColor' of undefined
    猜你喜欢
    • 1970-01-01
    • 2018-03-23
    • 2020-06-14
    • 2018-01-17
    • 1970-01-01
    • 2019-01-16
    • 1970-01-01
    • 2017-08-18
    • 2017-08-16
    相关资源
    最近更新 更多