Watch the bar turn green with TDD

When there are bugs, there is probably nothing more pleasing than figure out a way to:

  • Write a failing unit test that reproduces that bug.
  • Fix it and watch the bar turn green again, forever and ever 😉
La imagen tiene un atributo ALT vacío; su nombre de archivo es image-19-600x606.png

If your goal is to stay green for all of your existing tests by using the Test Driven Development (TDD) approach, you need to mentally train yourself to:

  • Think about the behavior around the next pieces of functionalities that you need in your code, then start moving towards your end goal in tiny steps and you will end up seeing tangible progress every moment.
  • Write unit test first, they shouldn’t be written as an extra chore when you finally have the code already working, this is called Test-Driven Development.

If you are designing an application to expose your data sources as REST API endpoints or as a unique GraphQL endpoint, TDD will definitely help you to design and maintain your APIs better. So if you are a principled developer who writes the tests that exercise the API first, you will discover where the APIs get complex to use before you even write the code, and you will be free to redesign much more easily than if you only add the tests afterwards.

Test the UI

Thanks goodness Create-React-App comes with Jest as its test runner and jest-dom for improved assertions. In combination with a state of the art package called the react-testing-library you will enjoy testing any React Component behavior or any Function or Hook’s inputs and outputs.

RTL Principle:

“The more your tests resemble the way your software is used, the more confidence they can give you.”

Another cool friendly kid in the hood is:

First thing you need to ask is: why we would want to mock fetch requests?

Because you use fetch to allow your App to interact with the rest of world, using:

  • Client-side JavaScript to trigger a network request to some local or remote backend API.
  • Server-side JavaScript talks to other local or remote servers.

Because you want to make sure your code doesn’t depends of flaky externalities, you want to ensure to produce the exact same output given the same inputs.

Setup your test environment

Initial configuration:

...
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.{js,jsx,ts,tsx}",
      "!src/**/index.{js,jsx,ts,tsx}",
      "!src/config/*",
      "!src/index.js",
      "!src/serviceWorker.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 90,
        "functions": 90,
        "lines": 90,
        "statements": 90
      }
    },
    "coverageReporters": [
      "text-summary"
    ]
  }
...
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "test-changed": "npm test --watchAll=false",
    "test-coverage-text": "npm test -- --coverage --coverageReporters=\"text\" --watchAll",
    "test-coverage-text-summary": "npm test -- --coverage --coverageReporters=\"text-summary\" --watchAll",
    "eject": "react-scripts eject"
  }
...
  "devDependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "@testing-library/react-hooks": "^3.4.1",
    "jest-fetch-mock": "^3.0.3",
    "react-test-renderer": "^16.13.1"
  }
...

If your app uses a browser API that you need to mock in your tests or if you need a global setup before running your tests, add a src/setupTests.js to your project. It will be automatically executed before running your tests.

// https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom/extend-expect";

// https://github.com/jefflau/jest-fetch-mock
import fetchMock from "jest-fetch-mock";
fetchMock.enableMocks();

Code Sample

git clone https://github.com/josoroma/reactjs-challenge.git
How I like to Write Integration Tests in React - YouTube

Routes

Components

You should test components’ behavior only.

App

States

States.lazy

Toolbar

Custom Hooks (renderHook())

You should test functions’ inputs and outputs only.

SearchValue Dispatch Hook

SearchValue State Hook

Context Reducer Function

Service Function

Util Function

Happy hacking!!!

Tip: If you are a great team player and the project is going through a long maintenance process, you should probably start writing tests for untested code, just seeing the Coverage Report as a G.A.M.E.

Use SWR with React Suspense

This article will explore how you should use the awesome useSWR hook for remote data fetching.

Why the SWR hooks for remote data fetching?

Because SWR (stale-while-revalidate) is a fully dedicated React Hooks library for remote data fetching. So basically, this is what SWR does:

  • First returns the data from cache (stale)
  • Then sends the fetch request (revalidate).
  • Finally comes with the up-to-date data again.

Why Suspense?

Because Suspense will definitely help you to maintain a consistent UI in the face of asynchronous dependencies. Something I prefer to call on-demand loaded React components.


All you need is to:

  • Set the suspense: true as one of the useSWR hook options.
  • Wrap your on demand route component inside of its own suspense component.
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import useSWR from "swr";

const App = () => {
  return (
    <Suspense fallback={<FallbackProgress />}>
      <Page />
    </Suspense>
  );
};

const FallbackProgress = () => <div>Loading...</div>;

const Page = () => {
  const { data } = useSWR(
    "https://jsonplaceholder.typicode.com/Todos/1",
    (req) => fetch(req).then((res) => res.json()),
    { suspense: true }
  );

  return <div>{data.title}</div>;
};

const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);

The whole thing put together

import React, { Suspense, lazy } from "react";

import { Progress } from "components";

const StatesLazy = lazy(() => import("./States.lazy"));

const States = () => (
  // Progress Component contains `data-testid="id-request-progress"`
  <Suspense fallback={<Progress />}>
    <div data-testid="id-states-lazy">
      <StatesLazy />
    </div>
  </Suspense>
);

export default States;
import React, { useEffect } from "react";
import Container from "@material-ui/core/Container";
import Divider from "@material-ui/core/Divider";
import { Link } from "react-router-dom";
import Typography from "@material-ui/core/Typography";
import { Redirect, useHistory } from "react-router-dom";
import _get from "lodash.get";
import useSWR from "swr";

import {
  useSearchValueDispatch,
  useSearchValueState,
} from "context/SearchValue";

import { endpoints } from "config";
import { searchUtil } from "utils";
import { csv2objFetcherService } from "services";

import { ContentCard, ContentMessage } from "components";

import useStyles from "./States.lazy.style";

const StatesLazy = () => {
  const classes = useStyles();
  const history = useHistory();

  const { searchValue } = useSearchValueState();
  const dispatch = useSearchValueDispatch();

  const requestURLConst = "for=state:*&DATE_CODE=1";

  const { data } = useSWR(
    `${endpoints.mainURL}${requestURLConst}`,
    csv2objFetcherService,
    { suspense: true }
  );

  const response = {
    status: _get(data, "status", 200),
    data: _get(data, "data", []),
    error: _get(data, "error", ""),
  };

  useEffect(() => {
    dispatch({ type: "setSearchValueReducer", payload: "" });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  if (response.status !== 200 && response.error !== "") {
    return (
      <div data-testid="id-states-response-error">
        <Redirect
          to={{
            pathname: history.location.pathname,
            state: { status: response.status, error: response.error },
          }}
        />
      </div>
    );
  }

  const searchResults = searchUtil(response.data, searchValue);

  if (searchResults && searchResults.length === 0) {
    return (
      <div data-testid="id-states-no-search-results">
        <ContentMessage
          type="message"
          title="No Results Found!"
          description="Let's ask again."
        />
      </div>
    );
  }

  return (
    <Container
      data-testid="id-states-container"
      className={classes.root}
      maxWidth="md"
    >
      <Typography variant="h1" className={classes.title}>
        States
      </Typography>
      <Divider className={classes.divider} />
      {searchResults.map(
        (state) =>
          state.NAME &&
          state.state && (
            <Link
              // Key
              key={Number(state.state)}
              // Rest of the Props
              className={classes.link}
              to={`${Number(state.state)}/counties`}
            >
              <ContentCard
                // Key
                key={Number(state.state)}
                // Rest of the Props
                density={state.DENSITY}
                population={state.POP}
                title={state.NAME}
              />
            </Link>
          )
      )}
    </Container>
  );
};

export default StatesLazy;

Wrapping up

As long as there is a child component using promises or acting asynchronous inside the Suspense component, it will be replaced by the component defined in the fallback prop.

Manage non connected components state

It is pretty simple, the React Context API helps to simplify the way you pass or share data across components.

Let’s start with using createContext() to offer a custom Provider component, including its Consumer component for interacting with your custom global state later:

src/context/SearchValue/SearchValueContext.js

Great thing about the Provider is that it is really useful to make the state available to all your child components:

src/App.js

The Consumer component goal is simple, it just consume the data that comes from the Provider, without any need for prop drilling your components.

src/routes/Routes.js

Just combine the Context API with the useReducer Hook to start enjoying the features of a custom and very simple global state management solution for your App.

The useReducer hook offers a simpler way to access and update your custom global state.

Reducer Actions
Dispatch Hook
Use the state or dispatch hooks to access or update your custom global state

Just call the dispatch method to make the useReducer hook perform an action based on the type that your method receives in its action argument.

dispatch() an update in your custom state

You can check out the complete code at the GitHub repo:

Feel free to try it out! Any insight is welcome!

Control your load prioritization in React

Let’s start talking about the idea behind bundle splitting in React , it is pretty simple as handling multiple bundles that can be dynamically loaded on demand at runtime. Basically, you should start:

  • Importing all your dynamic imports as regular components with React.lazy
  • Rendering all your lazy components inside a Suspense component.
1. React.lazy()
2. Suspense

The outcome is pretty awesome too. You will end up with smaller bundles thanks to this control resource load prioritization approach, which generally give you a major impact on your React App load time.

Complete code

import React, { Suspense, lazy } from "react";
import PropTypes from "prop-types";
import { Router, Switch, Route } from "react-router-dom";

import { ErrorHandler } from "context/ErrorHandler";
import { Progress } from "components";

// Route Components
const routes = [
  {
    exact: true,
    path: "/",
    component: lazy(() => import("components/States/States")),
  },
  {
    exact: true,
    path: "/:stateId/counties",
    component: lazy(() => import("components/Counties/Counties")),
  },
  {
    component: lazy(() => import("components/ErrorPage/ErrorPage")),
  },
];

const Routes = ({ history, Layout }) => (
  <>
    <Suspense fallback={<Progress />}>
      <Router history={history}>
        <ErrorHandler>
          <Layout.Toolbar />
          <Switch>
            {routes.map((route, i) => (
              <Route key={i} {...route} />
            ))}
          </Switch>
        </ErrorHandler>
      </Router>
    </Suspense>
  </>
);

Routes.propTypes = {
  history: PropTypes.object,
  Layout: PropTypes.any,
};

export default Routes;