Testing and Pre-commits
Published: 2021-02-19 10:16:34

_(This is an article written for a school project and shouldn't be trusted to the fullest. I'm still what you might refer to as a n00b)_

As a webdeveloper it's all about not getting too cocky; as soon as you show even an ounce of confidence in your code it _will_ bug out on you, and that's a promise. Which is why we as programmers need someone to hold our hand and make sure that the bugs are caught before anyone, other than us, sees them!

Enter stage right: _Testing!_

##How to write a test for a React component

Starting your project by using `npx create-react-app` is recommended, as it already has the testing packages installed:

`"@testing-library/jest-dom"`
`"@testing-library/react"`
`"@testing-library/user-event"`

The only package you need to install yourself is `npm i axios`, since we in this tutorial will be testing whether an axios fetch passes the test.

But why are we writing this test? We are basically writing it to see if our component can recieve data (in this case from an API) correctly and display it correctly as well. What the test is is a mockup of the actual database to see if our component actually recieves what we intended. If our mockup works successfully and displays what we expect, that should mean that our actual component and the actual data recieved will do so as well.

We'll be testing a very, very simple component with an axios function that performs a fetch from the [Star Wars API](https://swapi.dev/), where we get the first page of characters in a list. Here's the one I've written:

```javascript
import axios from "axios";
import {useState, useEffect} from "react";
import "./style/Characters.css";

export default function Characters(){
    var [pageOne, setPageOne] = useState([]);

    useEffect(function(){
        axios.get("https://swapi.dev/api/people/")
            .then(response => setPageOne(response.data));
    }, [setPageOne]);

    return(
        <main className="main">
            <h1 className="characters__heading">Characters</h1>
            <ul className="characters__list">
                {pageOne.results?.map(function(result){
                    return(
                        <>
                        <li key={result.name} className="characters__listItem">{result.name}</li>
                        </>
                    )
                })}
            </ul>
        </main>
    )
}
```

Now that we have our component, the next thing we need to do is write our test!

Start by creating a new folder in your src-folder named `__tests__` (remember to do double underscore on both sides). In this folder you create a new file with the same name as the component you want to test, but add a .test to the name.
My file is called `Characters.js` so my test file will be called `Characters.test.js`.
* Path for the test: `./src/__tests__/Characters.test.js` 
* Path for the component: `./src/components/Characters.js` for the component.

Furthermore we'll need a folder called `__mocks__` (yet again double underscores) in our root folder. In this folder we'll create a file by the name `axios.js`. 

`axios.js` contains an object that will try and mock the functionality of axios; axios, which essentially is a function containing a variety of methods. It looks like this:

```javascript
export default {
    get: jest.fn().mockResolvedValue(),
    post: jest.fn().mockRejectedValue()
};
```

Each method uses a jest function, as jest is our test library for this project.

### Writing the test: Let's do it!
Let me start from the end and show the finished result of our test file and the we'll go through it step by step.

```javascript
import {cleanup, render, waitFor, screen, act} from "@testing-library/react";
import Characters from "../components/Characters";
import mockedAxios from "axios";

jest.mock("axios");
afterEach(cleanup);

test("Test if axios fetch works for this component", async function(){
    var response = {
        data: {
            results: [
                {
                    name: "yoda"
                },
                {
                    name: "jar jar binks"
                }
            ]
        }
    };

    mockedAxios.get.mockResolvedValue(response);
    act(function(){
        render(<Characters />)
    });

    await waitFor(function(){
        var text = screen.getAllByText(/yoda/i);
        expect(text);
    });
});
```

Now let's get our hands in the dirt! In the test file we'll start out by importing a few specific testing methods from the testing library:

`import {cleanup, render, waitFor, screen, act} from "@testing-library/react";`

Additionally we'll import the component which we'll be testing, as well as axios. Axios will be called "mockedAxios" though, as it's not really axios we're working with.

`jest.mock("axios")` tells the test that it should use a mock of axios instead of axios itself.

`afterEach(cleanup)` runs a cleaup after each test, which means that the component which we've mounted in our test un-mounts. We clean up the render. This is to make sure we have a "clean" component each time we run the test.

Now to write the test. The first part is a string where you can write what the test does. You can write whatever your heart desires, but it's recommended to keep it short and factual.
Then we tell the test that it should run an asyncronous function.

In the function we start by creating a mock of what the actual data from the API looks like. It doesn't have to be long and complicated, but the structure has to be a correct replica. We'll be testing whether the data from "name" renders correctly, so that's the data we'll include in our mock.

Then we'll perform our mocked axios call by writing:
`mockedAxios.get.mockResolvedValue(response)`
So when the component uses axios with the get method we want it to return the mocked response instead of the real response from the API. It simply ignores the API and only focuses on our mocked version. 

`act` then simulates what happens in the browser; it acts as the browser would. Here we create a function which renders the component we'd like to "show" in the browser. 

Then we tell the test, through the `await waitFor`, that when everything within the async function has done its thing, then it can do its own thing...
which is to see if our data contains the results which we expect it to.

```javascript
await waitFor(function(){
    var text = screen.getAllByText(/yoda/i);
    expect(text);
});
```

The variable `text` searches all the text that gets printed it our mock browser to see if it contains a word we choose ourselves. I've written "yoda", as I know my object should return the word "yoda". When running the test now it should pass as what we test for matches a result from our mocked data. Had I written something else, let's say "darth maul", it wouldn't pass as that text does not appear in our object.

So! That's our test written! Now to check if it actually works. In the terminal you simply write `npm test`. It now runs all of your test documents and tests if everything matches. If it does, congratulations! If it doesn't, then you need to revisit your test and see where the problem could be. If your test seems to be written correctly, the problem is to be found in your component and it's not yet ready for that faithful push.

### How to use pre-commit hooks for tests

Jumping straight in! In your terminal write the command:
* `npm i husky`
* `npx husky add .husky/pre-commit`

This creates a folder in your root called `.husky`. In this you find the file `pre-commit` and here you add the line `npm test`, just wherever. I've put mine on line four.

Next install the cross env package by typing `npm install cross-env --save-dev` in your terminal. After it's completed saving go to package.json and add `cross-env CI=true` to the test script, like so:
`"test": "cross-env CI=true react-scripts test"`.
This tells jest to only run the test once, so it doesn't run it continuously.

So what does this all do? I bet you're wondering!

Well, this makes sure that if we try to commit something with a test that didn't pass then it won't let us. This is an amazing safety net to make sure that we only commit code that works!

Want to try it out? Go to your test and change where we wrote `/yoda/i`. Maybe try writing `/darth maul/i`. Now try to stage and commit your file.

Boom, you're not allowed to! Now not only do you have the test to make sure you're not at a complete loss but also have the hook to catch you if you thought you were good to go for that sweet, sweet commit.

## Formatting with Prettier

Lastly let's make that code look hella banger! We all have different habits when it comes to formatting our code, but when working in a team on a bigger project it can get quite frustrating with all the various formats jumbled together.

Introducing: Prettier!

Install her real quick by using `npm i prettier`. Now let's g et cracking!

Prettier is a package that helps format your code in the same way every time.

Firstly, add yet another script in `package.json`:
`"prettier": "prettier --write ."`
This makes sure that Prettier formats all files in your project. You could be more specific, but it's easier to add to your `.prettierignore` file instead. Which leads me to:

For Prettier you need to make two files in root. One called `.prettierignore`: this one acts pretty much the same as `.gitignore`; you just tell Prettier what files it shouldn't format. No touchy!
The second file is `.prettierrc.json` - this is where we tell Prettier in more detail how we'd like to format our code.

There's a plethora of options on what you can specify for your formatting - I'd suggest going to [Prettier's documentation]("https://prettier.io/docs/en/options.html") to read what you have to play around with.

```json
{
    "printWidth": 120,
    "tabWidth": 4,
    "quoteProps": "consistent",
    "useTabs": true,
    "semi": true,
    "jsxBracketSameLine": false,
    "singleQuote": false,
    "jsxSingleQuote": false,
    "bracketSpacing": false
}
```

To test that it actually works you just type `npm run prettier` and watch all of your formatting failures fade. Fabulous!

### How to use pre-commit hooks for Prettier

But wouldn't it be smart if you could make sure that you never commit any code that doesn't look fine and to a t? It absolutely would!

Start by going to `package.json` and move `prettier` from your dependencies and down to your devDependencies. Do the same with `eslint-config-prettier`

Now run `npx mrm lint-staged`. Now you can stage and commit your work and when you do that it automatically gets formatted. How clever! So now you'll never push any code that isn't looking fly and fresh.

**_TL;DR You do some stuff. Then you do some other stuff. Then magic. Now you're a web developer!_**