{"id":16736184,"url":"https://github.com/flexdinesh/testing-hooks","last_synced_at":"2025-03-21T21:31:41.810Z","repository":{"id":51775726,"uuid":"177279616","full_name":"flexdinesh/testing-hooks","owner":"flexdinesh","description":"Code examples to test custom React hooks with Enzyme","archived":false,"fork":false,"pushed_at":"2021-05-10T03:20:01.000Z","size":2042,"stargazers_count":24,"open_issues_count":11,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-18T05:34:21.597Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flexdinesh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-23T11:18:27.000Z","updated_at":"2022-11-04T03:53:59.000Z","dependencies_parsed_at":"2022-08-23T06:42:04.532Z","dependency_job_id":null,"html_url":"https://github.com/flexdinesh/testing-hooks","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flexdinesh%2Ftesting-hooks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flexdinesh%2Ftesting-hooks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flexdinesh%2Ftesting-hooks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flexdinesh%2Ftesting-hooks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flexdinesh","download_url":"https://codeload.github.com/flexdinesh/testing-hooks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244874331,"owners_count":20524577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T00:08:35.741Z","updated_at":"2025-03-21T21:31:41.420Z","avatar_url":"https://github.com/flexdinesh.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Testing-Hooks\n\nThis repo lays down different strategies to test React custom hooks and components that use hooks.\n\n## Test Strategies\n\nThere are broadly two strategies to test our React codebase.\n\n1. Testing user observable behaviour\n2. Testing implementation details\n\n### Testing user observable behaviour\n\nTesting user observable behaviour means writing tests against components that test\n\n- how the component is rendered\n- how the component is re-rendered when user interacts with the DOM\n- how props/state control what is rendered\n\nConsider the following component - `Greet`\n\n```jsx\nfunction Greet({ user = 'User' }) {\n  const [name, setName] = React.useState(user);\n\n  return \u003cdiv onClick={() =\u003e setName('Pinocchio')}\u003eHello, {name}!\u003c/div\u003e;\n}\n```\n\nTesting the user observable behaviour in `Greet` would mean\n\n- test if `Greet` is rendered without crashing\n- test if `Hello, User!` is rendered when user prop is not passed\n- test if `Hello, Bruce!` is rendered when `Bruce` is passed as value to `user` prop\n- test if the text changes to `Hello, Pinocchio!` when the user clicks on the element\n\n### Testing implementation details\n\nTesting implementation details means writing tests against state logic that test\n\n- how the state is initialized with default/prop values\n- how the state changes when handlers are invoked\n\nConsider the same component - `Greet`\n\n```jsx\nfunction Greet({ user = 'User' }) {\n  const [name, setName] = React.useState(user);\n\n  return \u003cdiv onClick={() =\u003e setName('Pinocchio')}\u003eHello, {name}!\u003c/div\u003e;\n}\n```\n\nTesting implementation details in `Greet` would mean\n\n- test if `name` is set to default value `User` when user prop is not passed to `Greet`\n- test if `name` is set to prop value when user prop is passed to `Greet`\n- test if `name` is updated when `setName` is invoked\n\n## Test custom hooks with Enzyme\n\n_Note: Please make sure your React version is `^16.8.5`. Hooks won't re-render components with enzyme shallow render in previous versions and the React team fixed it in this release. If your React version is below that, you might have to use enzyme mount and `.update()` your wrapper after each change to test the re-render._\n\nTesting implementation details might seem unnecessary and might even be considered as a bad practice when you are writing tests against components that contains presentational (UI) logic and render elements to the DOM. But **custom hooks** contain only **state logic** and it is imperative that we test the implementation details thoroughly so we know exactly how our custom hook will behave within a component.\n\nLet's write a custom hook to update and validate a form field.\n\n```js\n/* useFormField.js */\n\nimport React from 'react';\n\nfunction useFormField(initialVal = '') {\n  const [val, setVal] = React.useState(initialVal);\n  const [isValid, setValid] = React.useState(true);\n\n  function onChange(e) {\n    setVal(e.target.value);\n\n    if (!e.target.value) {\n      setValid(false);\n    } else if (!isValid) setValid(true);\n  }\n\n  return [val, onChange, isValid];\n}\n\nexport default useFormField;\n```\n\n**As great as custom hooks are in abstracting away re-usable logic in our code, they do have one limitation. Even though they are just JavaScript functions they will work only inside React components. You cannot just invoke them and write tests against what a hook returns. You have to wrap them inside a React component and test the values that it returns.**\n\n- custom hooks cannot be tested like JavaScript functions\n- custom hooks should be wrapped inside a React component to test its behaviour\n\nThanks to the composibility of hooks, we could pass a hook as a prop to a component and everything will work exactly as how it's supposed to work. We can write a wrapper component to render and test our hook.\n\n```jsx\n/* useFormField.test.js */\n\nfunction HookWrapper(props) {\n  const hook = props.hook ? props.hook() : undefined;\n  return \u003cdiv hook={hook} /\u003e;\n}\n```\n\nNow we can access the hook like a JavaScript object and test its behaviour.\n\n```jsx\n/* useFormField.test.js */\n\nimport React from 'react';\nimport { shallow } from 'enzyme';\nimport useFormField from './useFormField';\n\nfunction HookWrapper(props) {\n  const hook = props.hook ? props.hook() : undefined;\n  return \u003cdiv hook={hook} /\u003e;\n}\n\nit('should set init value', () =\u003e {\n  let wrapper = shallow(\u003cHookWrapper hook={() =\u003e useFormField('')} /\u003e);\n\n  let { hook } = wrapper.find('div').props();\n  let [val, onChange, isValid] = hook;\n  expect(val).toEqual('');\n\n  wrapper = shallow(\u003cHookWrapper hook={() =\u003e useFormField('marco')} /\u003e);\n\n  // destructuring objects - {} should be inside brackets - () to avoid syntax error\n  ({ hook } = wrapper.find('div').props());\n  [val, onChange, isValid] = hook;\n  expect(val).toEqual('marco');\n});\n```\n\nThe full test suite for `useFormField` custom hook will look like this.\n\n```jsx\n/* useFormField.test.js */\n\nimport React from 'react';\nimport { shallow } from 'enzyme';\nimport useFormField from './useFormField';\n\nfunction HookWrapper(props) {\n  const hook = props.hook ? props.hook() : undefined;\n  return \u003cdiv hook={hook} /\u003e;\n}\n\ndescribe('useFormField', () =\u003e {\n  it('should render', () =\u003e {\n    let wrapper = shallow(\u003cHookWrapper /\u003e);\n\n    expect(wrapper.exists()).toBeTruthy();\n  });\n\n  it('should set init value', () =\u003e {\n    let wrapper = shallow(\u003cHookWrapper hook={() =\u003e useFormField('')} /\u003e);\n\n    let { hook } = wrapper.find('div').props();\n    let [val, onChange, isValid] = hook;\n    expect(val).toEqual('');\n\n    wrapper = shallow(\u003cHookWrapper hook={() =\u003e useFormField('marco')} /\u003e);\n\n    // destructuring objects - {} should be inside brackets - () to avoid syntax error\n    ({ hook } = wrapper.find('div').props());\n    [val, onChange, isValid] = hook;\n    expect(val).toEqual('marco');\n  });\n\n  it('should set the right val value', () =\u003e {\n    let wrapper = shallow(\u003cHookWrapper hook={() =\u003e useFormField('marco')} /\u003e);\n\n    let { hook } = wrapper.find('div').props();\n    let [val, onChange, isValid] = hook;\n    expect(val).toEqual('marco');\n\n    onChange({ target: { value: 'polo' } });\n\n    ({ hook } = wrapper.find('div').props());\n    [val, onChange, isValid] = hook;\n    expect(val).toEqual('polo');\n  });\n\n  it('should set the right isValid value', () =\u003e {\n    let wrapper = shallow(\u003cHookWrapper hook={() =\u003e useFormField('marco')} /\u003e);\n\n    let { hook } = wrapper.find('div').props();\n    let [val, onChange, isValid] = hook;\n    expect(val).toEqual('marco');\n    expect(isValid).toEqual(true);\n\n    onChange({ target: { value: 'polo' } });\n\n    ({ hook } = wrapper.find('div').props());\n    [val, onChange, isValid] = hook;\n    expect(val).toEqual('polo');\n    expect(isValid).toEqual(true);\n\n    onChange({ target: { value: '' } });\n\n    ({ hook } = wrapper.find('div').props());\n    [val, onChange, isValid] = hook;\n    expect(val).toEqual('');\n    expect(isValid).toEqual(false);\n  });\n});\n```\n\nRendering the custom hook and accessing it as a prop should give us full access to its return values.\n\nHappy testing!\n\n## License\n\nMIT © Dinesh Pandiyan\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflexdinesh%2Ftesting-hooks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflexdinesh%2Ftesting-hooks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflexdinesh%2Ftesting-hooks/lists"}