Testing and formatting before commit
Tags: react, testing, precommit, formatting
Published: 2021-02-17 12:50:35

When we commit code, it's important that our code doesn't have errors and does exactly what we expect it to, and if the code is publicly available (like on GitHub), it also matters how the code looks and that it is easy to read by others.

##### Code that behaves properly and isn't buggy
To prevent errors in our code and make sure our code behaves as we expect, we test our code with unit-testing/testing-libraries.

Luckily for us using React, it comes with a testing library that we can easily use and create tests with.

##### Readable and nice looking code
To make our code readable and nice to look at, we format our code by using spaces, linebreaks and tab indentation amongst others.

This can be automated for us with the use of an npm package called `Prettier` (there are propably many others out there, but this is what we'll be using in this tutorial).

##### Doing it automatically before we commit
When testing, we must run the command `npm test` and when we need to format our code, we must run `npm run prettier`, but we must manually do this before every commit we make to make sure we don't commit wrong/error proned/ugly/hard to read -code.

Wouldn't it be great if we could do this automatically?

Guess what! We can... Wuhuu!

I will take you through a little journey where we will be looking at how to:

- [Create tests in React](#create-a-simple-test)
- [Use prettier and set rules for formatting](#prettier)
- [Create pre-commit hooks for prettier](#prettier-and-the-pre-commit-hook)
- [Create pre-commit hooks for tests](#test-before-commit)

## Creating a simple test

#### Start with a React project

In this tutorial we will use `create-react-app` which (when installed) already includes a testing library (`"@testing-library/react"`).

Start by creating a folder, named `test-and-format`.
You can name it whatever you want, but make sure the name is all lowercase!

I use VSCode as my editor, but you can use whichever editor you prefer.

Open up VSCode with the `test-and-format` folder as your project root.

Make sure the folder is completely empty, and then in the terminal, run:

`npx create-react-app .`

### Create a simple component

I chose to make a simple `Card` -component. So create a file named `Card.js` and add this code inside:

```javascript
function Card(){
	return null;
}

export default Card;
```
This component does absolutely nothing yet (it only returns null). Don't worry, we will create the component when we have made our test.

#### Add Card to App
Clean up your `App.js` so it looks something like this (also delete its dependencies):

```javascript
import './App.css';

function App() {
  return (
    
  );
}

export default App;
```

import your `Card` -component and return it:

```javascript
import './App.css';
// Add the import
import Card from './Card';

function App() {
  return (
    // return the Card
    <Card/>
  );
}

export default App;
```

### Create a simple test

Delete `App.test.js` (because it will fail since we changed the content of `App.js`).

Now we are ready to create our test for our `Card` -component.

Create a file named `Card.test.js` and add the following code:

```javascript
// Import Reacts render and screen which is used by our test
import {render, screen} from "@testing-library/react";
// Import our Card -component
import Card from "./Card";

// The test itself
test("Checking if 'My Title' exists in the component", () => {

	// We are rendering our component passing in a title
	// and a text as props (the attributes)
	render(<Card title="My Title" text="Something"/>);

	// Parsing the "screen" for the text "my title"
	// The "i" in the regular expressions means "ignore upper/lower-case"
	var myTitle = screen.getByText(/my title/i);

	// This is what we expect ("my title" to be in the document)
	expect(myTitle).toBeInTheDocument();
});
```

Run `npm test` to see if our test passes.

It will fail because our component isn't completed yet (remember, it returns `null`!)

So let's finish it:

```javascript
function Card({title, text}){
	return (
		<article className="Card">
			<h1>{title}</h1>
			<p>{text}</p>
		</article>
	);
}

export default Card;
```

Run `npm test` again and see that our test now passes.

We have created this project with Test Driven Design (TDD) in mind, so we wrote the test first and then the component.

The idea with TDD is that we create our tests with specific criterias for the components first, and these criterias must then be met when we create our component.

This is to make sure we create a component that when the criterias are met, just works without flaws or problems that can break something further down the road, especially when working on a large project.

To illustrate this, let's pretend we made a small mistake when creating our component:

```javascript
function Card({title, text}){
	return (
		<article className="Card">
			// Here I forgot the curly braces around title:
			<h1>title</h1>
			<p>{text}</p>
		</article>
	);
}

export default Card;
```

When we now run our test with `npm test` it will fail.
It fails because the actual text rendered is "title" and not "My Title" because "title" is hardcoded, but we created the component with props in mind and expected that the `title` -prop contained the actual text: "My Title":

```
Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.828 s
Ran all test suites.
npm ERR! Test failed.  See above for more details.
```

We can now inspect what is wrong and (hopefully) find our little mistake, correct it, and run the test again to see that it now passes:

If we scroll up a bit in the terminal, we can see where the error happened:

```
      4 | test("Checking if 'My Title' exists in the component", () => {
      5 |       render(<Card title="My Title" text="Something" />);
    > 6 |       var myTitle = screen.getByText(/my title/i);
        |                            ^
      7 |       expect(myTitle).toBeInTheDocument();
      8 | });
      9 |
```

It fails on line 6 in our test, which means that the text "my title" was not found anywhere in the rendered component (whether lowercase or uppercase).

If we scroll up even further in the terminal, we see what is actually rendered:

```
    <body>
      <div>
        <article
          class="Card"
        >
          <h1>
            title
          </h1>
          <p>
            Something
          </p>
        </article>
      </div>
    </body>
```
And here we can see that the text "my title" is not anywhere in the markup (HTML).

Let's take a look at our component and see if we can spot what is wrong:

```javascript
function Card({ title, text }) {
	return (
		<article className="Card">
			<h1>title</h1>
			<p>{text}</p>
		</article>
	);
}

export default Card;
```
Surely we can see that "title" is hardcoded, but our intention was to use the title prop, so let's add the curly braces and fix our little mistake:

```javascript
function Card({ title, text }) {
	return (
		<article className="Card">
			<h1>{title}</h1>
			<p>{text}</p>
		</article>
	);
}

export default Card;
```

Let's run the test and see that everything works perfectly:

```
 PASS  src/components/Card.test.js
  √ Checking if 'My Title' exists in the component (29 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.213 s
Ran all test suites.
```

This is all good, and we can test our components to see if they fail or pass.

Before we dig into pre-commits let's take a look at formatting our code with prettier (we ultimately also want our code to format nicely before we commit, right?).

## Prettier

To format our code we use prettier and we need to install following packages:

- prettier
- eslint-config-prettier

The `eslint-config-prettier` is needed for prettier to play nicely with ESLint.
It just disables unnecessary rules or rules that could conflict with `Prettier`. React (`create-react-app`) comes with ESLint pre-installed, so we need this package.

Install the packages with this command:

`npm i -D prettier eslint-config-prettier`

or

`npm i --save-dev prettier eslint-config-prettier`

#### Ignore files you don't want prettyfied
As default, Prettier will format **all** files in our project, so if there are any files we don't want Prettier to run through, we can define them in an ignore file.

Create a file named `.prettierignore` and define files/folders that Prettier will ignore (it works just like `.gitignore` if that is familiar to you):

Example content:
```
node_modules
build
coverage
.vscode
```

As an absolute minimium, you should add `node_modules` to the ignore file, because the amount of files inside it is enormous, and it would take forever to run through them all (it is also unnecessary to prettify other developers code).

#### Configure Prettier to your likings
I want to ask you a couple of questions:

- Do you use spaces inside your brackets when destructuring?
- Do you use tabs or spaces when indenting?
- Do you use double (`"`) or single (`'`) -quotes?

All of these things can be configured to make Prettier do all of these for you automatically.

How?

Create a file named `.prettierrc.json` and add properties which defines the behaviour of Prettier (set the rules for formatting)

Example content (see a complete list of rules [here](https://prettier.io/docs/en/options.html)):
```json
{
	"printWidth": 120,
	"useTabs": true,
	"semi": true,
	"quoteProps": "consistent",
	"trailingComma": "none",
	"bracketSpacing": true,
	"arrowParens": "avoid"
}
```

The time has come for our pre-commit hooks (finally!)...

# Run commands before a commit
What we wanted was to run both Prettier automatically and all our tests automatically, so we don't have to run `npm run prettier` and then `npm test` manually everytime we commit.

So let's take a look at how we can achieve this:

### Prettier and the pre commit hook

The pre-commit hook enables you to run commands BEFORE a commit.

To enable the prettier before a commit, we must run this command in the terminal:

`npx mrm lint-staged`

This installs a package called `husky` along with `lint-staged`.

If we then add a property to `scripts` in the `package.json` file:

`"prettier": "prettier --write ."`

we can prettify all our files manually (according to our specifications in `.prettierrc.json`) everytime we run this command in the terminal:

`npm run prettier`

### Test before commit

To make our tests run:

We need a husky folder, which should ultimately contain our pre-commit hook for the tests. We create it with this command:

`npx husky install`

Then create a pre-commit file (with the pre-commit hook inside):

`npx husky add .husky/pre-commit "npm test"`

In my case `npx husky add .husky/pre-commit "npm test"` did not work properly (it did not create the pre-commit file inside the husky folder, but instead gave me this message):

(if it worked for you, you can [skip to the next section](#add-prettier-to-the-commit-file))

```
$ npx husky add .husky/pre-commit "npm test"
Usage

  husky install [dir] (default: .husky)
  husky uninstall
  husky add <file> [cmd]

Examples

  husky install
  husky install .config/husky

  husky add .husky/pre-commit
  husky add .husky/pre-commit "npm test"
  husky add .config/husky/pre-commit "npm test"
```

So to make it work, I had to create the file first:

`npx husky add .husky/pre-commit`

Then open the file (`.husky/pre-commit`) and manually add `npm test` on it's own line in the file:

```
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm test
```

### Add prettier to the commit file

Now, the only thing the pre-commit file does is run the `npm test` command. We also want to run the prettier command (`npm run prettier`), so let's add it:

```
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run prettier
npm test
```

### Make the commit actually commit when all tests pass

If at this point we try to commit something, the files will be prettyfied and our tests should run, but... the test will "hang" and never commit anything...

To fix this, we must do one more step. Install the cross-env package:

`npm i -D cross-env`

or

`npm i --save-dev cross-env`

and in package.json under `scripts` we must change:

`"test": "react-scripts test"`

to

`"test": "cross-env CI=true react-scripts test"`

This will make sure that when we run the test (either by committing or with `npm test`) the test will "break out" of its "wait state".

You can give it a try by running `npm test`:

- with `"cross-env CI=true react-scripts test"`

and

- with `"react-scripts test"`

and see the difference for yourself.

#### What we have made

We have now succesfully created an automated feature where everytime we commit, our files are formatted nicely and consistently, and all tests are run:

if the tests pass: perform the commit

if the tests fail: commit will not be allowed!

This is what we want and if this works for you, congratulations, you now have functionality that makes sure you never commit "crappy" code (if your tests are created properly, that is).