Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- "Injecting" configuration into a create-react-app is done with environmental
- variables. I always have at least one for the root of my REST API or GraphQL
- endpoint.
- When running in development, I set that variable to point at a local version
- of the backend server I have running on my laptop. That's some other project
- that I do whatever I need to in order to get it running and available to accept
- requests from my React app.
- If I wanted to start simple with static JSON files, maybe I would use something
- like json-server, but I normally have an existing backend already available for
- my apps.
- There's also nothing stopping you from just putting static JSON files in the
- `public` directory and setting your variable to those filenames. They will be
- fetched over the local "network" but big deal.
- The kind of testing I'm doing here is ad-hoc in nature and I won't even notice
- the cost of hitting the network because it's all on localhost.
- To do this, stick your variables in a `.env` file (they have to start with
- `REACT_APP_`):
- ```
- REACT_APP_DATA_URL=data.json
- ```
- Then use them in your code like this:
- ```
- export default class SomeComponentThatFetchesData extends Component {
- state = { data: [] };
- async componentDidMount() {
- const response = await fetch(process.env.REACT_APP_DATA_URL);
- if (response.ok) {
- this.setState({ data: await response.json() });
- }
- }
- render() {
- return <SomeComponentThatPresentsData data={this.state.data} />;
- }
- }
- ```
- Unfortunately, it's now impossible to test that component without accessing the
- network. That's fine for exploratory testing, but not unit testing.
- First of all, I don't normally write unit tests for React components. When I do,
- I understand I have to write them in a style that makes them as easy-to-test as
- possible or I'll start making excuses and stop writing tests.
- Standard "best practices" apply in JavaScript just like in every other
- programming language. Complicated business logic shouldn't be embedded in user
- interface code. Low-level dependencies (like networking and data access) should
- be encapsulated in some sort of abstractions.
- To me, that means complicated "business logic" gets written in (or extracted to)
- their own modules so that they can be easily tested. Networking/data access
- logic likewise goes in their own modules. The actual data is usually passed in
- to the business logic modules via normal function or constructor arguments
- making them very easy to test.
- OK, so let's say I actually want to test a React component. That component needs
- to be written in a way that's testable.
- Components that use the network like I showed above can't be easily tested. But
- did you notice how that component didn't actually render anything? All it does
- is fetch data and leaves the rendering to some other component.
- Being able to nest (compose) components like this is what makes React so
- awesome. It's easy to think of this as a purely a visual thing, but I think it's
- also a way to structure my code.
- Abstractly, I think of my application as a tree of components with the root
- component as the "core" of my application. It's the most application-specific
- component in my entire app and can never be reused because it depends on every
- single other component it indirectly contains (is composed of).
- If I think of the component tree as a dependency tree, it's even easier for me
- to visualize nodes closer to the root as having more dependencies than leaf
- nodes and are, therefore, harder to test.
- For unit testing (and many other) purposes, leaf nodes are the best kinds of
- components. Those components have no dependencies _except_ for the props passed
- in to them by their parent components.
- These components are like functions that don't use any global variables and only
- operate on the arguments passed in to them. Testing functions like that is so
- _easy_.
- In React, there's this concept of *presentation* components and *container*
- components. You can tell by the name that container components contain things so
- they can't be leaf nodes. Presentation components are the opposite. They're
- meant to be contained. Their job is to present data passed in to them via props.
- To make things very confusing, presentation components can contain container
- components and that's totally fine. It will eventually click.
- The component I showed above is a container component. This is what the
- presentation component it uses might look like:
- ```
- export default class SomeComponentThatPresentsData extends Component {
- render() {
- return (
- <ul>
- {this.props.data
- .filter(item => item.visible)
- .map(item => <li key={item.id}>{item.value}</li>)}
- </ul>
- );
- }
- }
- ```
- See how this component never fetches data? The data is basically "injected" via
- props. That makes this component trivial to test:
- ```
- import React from 'react';
- import { shallow } from 'enzyme';
- import SomeComponentThatPresentsData from './SomeComponentThatPresentsData';
- describe('<SomeComponentThatPresentsData />', () => {
- it('only renders visible items', () => {
- const data = [
- { id: 1, visible: true, value: 'foo' },
- { id: 2, visible: false, value: 'bar' },
- { id: 3, visible: true, value: 'baz' },
- ];
- const wrapper = shallow(<SomeComponentThatPresentsData data={data} />);
- expect(wrapper.find('li').length).toBe(2);
- });
- });
- ```
- All network access (even to localhost) is completely avoided and this test runs
- as fast as it can. I can even see everything I need to in order to reason about
- what this test is doing because every piece of data the component uses is right
- here in the test itself.
- I made the decision to make my presentation component testable, but not my
- container component. I'm not saying I never test container components.
- create-react-app generates a test to ensure `<App />` doesn't crash when
- rendering. I think that's cool and try to keep that test passing, but if that
- ends up being difficult, it gets deleted. My `<App />` component usually ends up
- being where all the routes are set up so not usually something that needs to be
- tested, IMO.
- There is nothing in JavaScript or React that enforces any of this, though. Itβs
- just a style that works for me that I learned from the community.
- Final note: I really like Redux. When I use Redux with React, my code looks a
- little different than what I wrote above.
- The react-redux package has a `connect()` function that "injects" props into
- components. It takes a small amount of "boilerplate" to wire everything
- together, but I think it's reasonable and like how explicit it requires me to
- be. I kind of think of `connect()` as generating my container components for me.
- I do not recommend learning React and Redux at the same time.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement