Advertisement
Guest User

Untitled

a guest
Aug 16th, 2017
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.62 KB | None | 0 0
  1. "Injecting" configuration into a create-react-app is done with environmental
  2. variables. I always have at least one for the root of my REST API or GraphQL
  3. endpoint.
  4.  
  5. When running in development, I set that variable to point at a local version
  6. of the backend server I have running on my laptop. That's some other project
  7. that I do whatever I need to in order to get it running and available to accept
  8. requests from my React app.
  9.  
  10. If I wanted to start simple with static JSON files, maybe I would use something
  11. like json-server, but I normally have an existing backend already available for
  12. my apps.
  13.  
  14. There's also nothing stopping you from just putting static JSON files in the
  15. `public` directory and setting your variable to those filenames. They will be
  16. fetched over the local "network" but big deal.
  17.  
  18. The kind of testing I'm doing here is ad-hoc in nature and I won't even notice
  19. the cost of hitting the network because it's all on localhost.
  20.  
  21. To do this, stick your variables in a `.env` file (they have to start with
  22. `REACT_APP_`):
  23.  
  24. ```
  25. REACT_APP_DATA_URL=data.json
  26. ```
  27.  
  28. Then use them in your code like this:
  29.  
  30. ```
  31. export default class SomeComponentThatFetchesData extends Component {
  32. state = { data: [] };
  33.  
  34. async componentDidMount() {
  35. const response = await fetch(process.env.REACT_APP_DATA_URL);
  36.  
  37. if (response.ok) {
  38. this.setState({ data: await response.json() });
  39. }
  40. }
  41.  
  42. render() {
  43. return <SomeComponentThatPresentsData data={this.state.data} />;
  44. }
  45. }
  46. ```
  47.  
  48. Unfortunately, it's now impossible to test that component without accessing the
  49. network. That's fine for exploratory testing, but not unit testing.
  50.  
  51. First of all, I don't normally write unit tests for React components. When I do,
  52. I understand I have to write them in a style that makes them as easy-to-test as
  53. possible or I'll start making excuses and stop writing tests.
  54.  
  55. Standard "best practices" apply in JavaScript just like in every other
  56. programming language. Complicated business logic shouldn't be embedded in user
  57. interface code. Low-level dependencies (like networking and data access) should
  58. be encapsulated in some sort of abstractions.
  59.  
  60. To me, that means complicated "business logic" gets written in (or extracted to)
  61. their own modules so that they can be easily tested. Networking/data access
  62. logic likewise goes in their own modules. The actual data is usually passed in
  63. to the business logic modules via normal function or constructor arguments
  64. making them very easy to test.
  65.  
  66. OK, so let's say I actually want to test a React component. That component needs
  67. to be written in a way that's testable.
  68.  
  69. Components that use the network like I showed above can't be easily tested. But
  70. did you notice how that component didn't actually render anything? All it does
  71. is fetch data and leaves the rendering to some other component.
  72.  
  73. Being able to nest (compose) components like this is what makes React so
  74. awesome. It's easy to think of this as a purely a visual thing, but I think it's
  75. also a way to structure my code.
  76.  
  77. Abstractly, I think of my application as a tree of components with the root
  78. component as the "core" of my application. It's the most application-specific
  79. component in my entire app and can never be reused because it depends on every
  80. single other component it indirectly contains (is composed of).
  81.  
  82. If I think of the component tree as a dependency tree, it's even easier for me
  83. to visualize nodes closer to the root as having more dependencies than leaf
  84. nodes and are, therefore, harder to test.
  85.  
  86. For unit testing (and many other) purposes, leaf nodes are the best kinds of
  87. components. Those components have no dependencies _except_ for the props passed
  88. in to them by their parent components.
  89.  
  90. These components are like functions that don't use any global variables and only
  91. operate on the arguments passed in to them. Testing functions like that is so
  92. _easy_.
  93.  
  94. In React, there's this concept of *presentation* components and *container*
  95. components. You can tell by the name that container components contain things so
  96. they can't be leaf nodes. Presentation components are the opposite. They're
  97. meant to be contained. Their job is to present data passed in to them via props.
  98.  
  99. To make things very confusing, presentation components can contain container
  100. components and that's totally fine. It will eventually click.
  101.  
  102. The component I showed above is a container component. This is what the
  103. presentation component it uses might look like:
  104.  
  105. ```
  106. export default class SomeComponentThatPresentsData extends Component {
  107. render() {
  108. return (
  109. <ul>
  110. {this.props.data
  111. .filter(item => item.visible)
  112. .map(item => <li key={item.id}>{item.value}</li>)}
  113. </ul>
  114. );
  115. }
  116. }
  117. ```
  118.  
  119. See how this component never fetches data? The data is basically "injected" via
  120. props. That makes this component trivial to test:
  121.  
  122. ```
  123. import React from 'react';
  124. import { shallow } from 'enzyme';
  125.  
  126. import SomeComponentThatPresentsData from './SomeComponentThatPresentsData';
  127.  
  128. describe('<SomeComponentThatPresentsData />', () => {
  129. it('only renders visible items', () => {
  130. const data = [
  131. { id: 1, visible: true, value: 'foo' },
  132. { id: 2, visible: false, value: 'bar' },
  133. { id: 3, visible: true, value: 'baz' },
  134. ];
  135.  
  136. const wrapper = shallow(<SomeComponentThatPresentsData data={data} />);
  137.  
  138. expect(wrapper.find('li').length).toBe(2);
  139. });
  140. });
  141. ```
  142.  
  143. All network access (even to localhost) is completely avoided and this test runs
  144. as fast as it can. I can even see everything I need to in order to reason about
  145. what this test is doing because every piece of data the component uses is right
  146. here in the test itself.
  147.  
  148. I made the decision to make my presentation component testable, but not my
  149. container component. I'm not saying I never test container components.
  150. create-react-app generates a test to ensure `<App />` doesn't crash when
  151. rendering. I think that's cool and try to keep that test passing, but if that
  152. ends up being difficult, it gets deleted. My `<App />` component usually ends up
  153. being where all the routes are set up so not usually something that needs to be
  154. tested, IMO.
  155.  
  156. There is nothing in JavaScript or React that enforces any of this, though. It’s
  157. just a style that works for me that I learned from the community.
  158.  
  159. Final note: I really like Redux. When I use Redux with React, my code looks a
  160. little different than what I wrote above.
  161.  
  162. The react-redux package has a `connect()` function that "injects" props into
  163. components. It takes a small amount of "boilerplate" to wire everything
  164. together, but I think it's reasonable and like how explicit it requires me to
  165. be. I kind of think of `connect()` as generating my container components for me.
  166.  
  167. I do not recommend learning React and Redux at the same time.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement