Advertisement
Guest User

Untitled

a guest
Aug 21st, 2019
176
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 4.59 KB | None | 0 0
  1. # Problem
  2.  
  3. Unit testing with `useGetFetch` requires mounting to fully test effects, which sucks if you're doing something with connected components downstream. But if you consider the custom hook is well tested, you can also ignore it. I've found a few patterns that allow unit testing the rendering or click handlers while ignoring the bulk of the logic for `useGetFetch` and avoiding the use of mount.
  4.  
  5. To start, a typical component that uses `useGetFetch` probably looks like the following:
  6.  
  7. ```js
  8. export function Component({ match }) {
  9. const [data, loading] = useGetFetch(`/api/v1/careplan?=subject=${match.params.clientId}`);
  10.  
  11. // Handle initial/loading states
  12. if (!data || loading) {
  13. return <LoadingIndicator />;
  14. }
  15.  
  16. // Verify data, e.g., from a FHIR endpoint
  17. if (!Array.isArray(data.entry) {
  18. return <ErrorWidget />;
  19. }
  20.  
  21. // At this point, all children components can be sure the data is valid.
  22. return <AdvancedSearch { ...props } />;
  23. }
  24. ```
  25.  
  26. To test this, you'd have to use stub the fetch, now easily done with FetchContext.
  27.  
  28. ```js
  29. import { expect } from 'chai';
  30. import sinon from 'sinon';
  31. import { mount } from 'enzyme';
  32.  
  33. import { Component } from './Component';
  34. import { FetchContext } from './useFetch';
  35.  
  36. describe('Component', () => {
  37. let fetch;
  38. beforeEach(() => {
  39. fetch = sinon.stub();
  40. })
  41. it('renders a loading indicator initially', () => {
  42. const wrapper = mount(
  43. <FetchContext.Provider value={ fetch }>
  44. <Component match={ { params: { clientId: 'foo' } } } />
  45. </FetchContext.Provider>
  46. );
  47. expect(wrapper.find(LoadingIndicator)).to.have.length(1);
  48. });
  49. it('renders an error widget if fetching returns an invalid shape', async () => {
  50. fetch.resolves({ message: 'error' });
  51. const wrapper = mount(
  52. <FetchContext.Provider value={ fetch }>
  53. <Component match={ { params: { clientId: 'foo' } } } />
  54. </FetchContext.Provider>
  55. );
  56. await 0;
  57. wrapper.update();
  58. expect(wrapper.find(ErrorWidget)).to.have.length(1);
  59. });
  60. it('renders AdvancedSearch with the data', async () => {
  61. fetch.resolves({ entry: [] });
  62. // Uh-oh, this will throw because AdvancedSearch is connected to the store.
  63. const wrapper = mount(
  64. <FetchContext.Provider value={ fetch }>
  65. <Component match={ { params: { clientId: 'foo' } } } />
  66. </FetchContext.Provider>
  67. );
  68. await 0;
  69. wrapper.update();
  70. expect(wrapper.find(AdvancedSearch)).to.have.length(1);
  71. });
  72. });
  73. ```
  74.  
  75. However, when mounting, components wrapped in the connect HOC will throw an error to wrap your components in a `<Provider />`.
  76.  
  77. # Solution
  78.  
  79. To fix/work around this, you can use test props to bypass certain conditionals, regardless of the actual result of `useGetFetch`, since you can be sure it will handle loading and errors correctly. *(Unfortunately, we can't provide test data to `useGetFetch` to return because the Context API only works when mounted.)
  80.  
  81. ```js
  82. export function Component({
  83. match,
  84. __TEST_DATA__ // <- test prop
  85. }) {
  86. const [dataFromFetch, loading] = useGetFetch(`/api/v1/careplan?=subject=${match.params.clientId}`);
  87. const data = __TEST_DATA__ || dataFromFetch;
  88.  
  89. // If there is no test data, proceed with the usual rendering logic.
  90. if (!data || loading) {
  91. return <LoadingIndicator />;
  92. }
  93.  
  94. // You can also verify the __TEST_DATA__ with shallow rendering as well.
  95. if (!Array.isArray(data.entry) {
  96. return <ErrorWidget />;
  97. }
  98.  
  99. // Finally, since we've reached this point with __TEST_DATA__ without depending on useEffect,
  100. // you can use shallow rendering to test this.
  101. return <AdvancedSearch { ...props } data={ data.entry } />;
  102. }
  103. ```
  104.  
  105. So, to test this, you can use shallow rendering:
  106.  
  107. ```js
  108. import { expect } from 'chai';
  109. import { shallow } from 'enzyme';
  110.  
  111. import { Component } from './Component';
  112.  
  113. describe('Component', () => {
  114. it('renders AdvancedSearch with the data', () => {
  115. // Yay, this works!
  116. const wrapper = shallow(
  117. <Component
  118. match={ { params: { clientId: 'foo' } } }
  119. __TEST_DATA__={ { entry: [] } } />
  120. );
  121. expect(wrapper.find(AdvancedSearch).props().data).to.eql([]);
  122. });
  123. });
  124. ```
  125.  
  126. And there you have it! A way to shallow render and test components using `useGetFetch`.
  127.  
  128. # TL;DR
  129.  
  130. For legacy components still using Redux, using mount to test loading/error states is still easy enough. But when getting into the bulk of the render logic, it might be infeasible to mount, so you can use test props to bypass certain conditionals.
  131.  
  132. For new components with children that don't depend on Redux, it's fairly easy to just use mount. However, you can still use the same patterns as above to avoid rendering deep sub-trees.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement