> For the complete documentation index, see [llms.txt](https://tutorial.specian.co.uk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tutorial.specian.co.uk/project-setup/jest.md).

# Testing

### Setting up Jest with TypeScript

To keep thing clear and simple, we will move all the code related to the app to a new `src` folder. For now, we just have `index.ts`, but the number of files will quickly grow.

```
$ cd packages/api
$ mkdir src
$ mv index.ts src
```

Create `jest.config.js` in `packages/api` with the following content:

{% code title="jest.config.js" %}

```typescript
module.exports = {
  preset: 'ts-jest',
  rootDir: 'src',
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
  testEnvironment: 'node',
  moduleFileExtensions: ['js', 'ts', 'tsx'],
  moduleDirectories: ['node_modules'],
  coverageReporters: ['html'],
  setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
  globals: {
    'ts-jest': {
      tsConfig: 'tsconfig.json',
    },
  },
};
```

{% endcode %}

Create `setupTests.ts` and leave it empty. Anything in this file is run before the tests. We will need it later.

Now is also a good time to create our `tsconfig.json` file in `packages/api`.

```
$ cd packages/api
$ tsc --init
$ yarn add esnext
```

Replace the content with the following:

{% code title="tsconfig.json" %}

```javascript
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "sourceMap": true,
    "declaration": true,
    "strictNullChecks": true,
    "noUnusedLocals": false,
    "allowUnusedLabels": false,
    "noUnusedParameters": false,
    "pretty": true,
    "skipLibCheck": true,
    "lib": ["es2015", "esnext.asynciterable"]
  },
  "sourceMap": true,
  "outDir": ".build",
  "moduleResolution": "node",
  "rootDir": "./src"
}
```

{% endcode %}

### Writing tests

There are 2 things that are notoriously difficult when unit testing:

1. Mocking imports
2. Testing asynchronous code

Let's write a function to fetch all the emojis available on GitHub.

First, install the required dependencies

```
$ cd packages/api
$ yarn add node-fetch @types/node-fetch
$ yarn add jest @types/jest ts-jest -D
```

Fetch the emojis:

{% code title="index.ts" %}

```typescript
import fetch from "node-fetch";

const getEmoji = async (emoji: string): Promise<string> => {
    const response = await fetch("https://api.github.com/emojis");
    const data = await response.json();
    return data[emoji];
};

export { getEmoji };
```

{% endcode %}

Now we have a problem, how do we mock the request to GitHub? We don't want to send the request each time we run the test.

### Mocking dependencies is hard

A lot of people struggle with setting up all the mocking required to isolate a function. They would end up writing a large amount of code to setup the test, but little to test the actual function.

### Dependencies injection

Injecting dependencies allows us to arbitrarily set a dependency at any time. This is perfect for unit testing. We can also leverage some TypeScript functionalities around classes.

Let's get started:

```
$ yarn add typedi
```

Create a new file  `Emoji.ts`with a class `Emoji`

{% code title="Emoji.ts" %}

```typescript
import fetch from "node-fetch";
import { Container } from "typedi";

class Emoji {
  private readonly _fetch: typeof fetch = Container.get("fetch");
  async get(emoji: string): Promise<string> {
    const response = await this._fetch("https://api.github.com/emojis");
    const data = await response.json();
    return data[emoji];
  }
}

export { Emoji };
```

{% endcode %}

By using `private readonly _fetch: typeof fetch = Container.get("fetch");` we can now set `fetch` to anything we'd like.

{% code title="Emoji.spec.ts" %}

```typescript
import { Emoji } from "./Emoji";
import { Container } from "typedi";

describe("fetchRepositories function", () => {
  it("should fetch and return emoji passed as a param", async () => {
    // Arrange: setup everything we need
    const computer = "mocked url";
    const data = { computer };
    const json = jest.fn().mockResolvedValue(data);
    const fetch = jest.fn(() => ({ json }));
    Container.set("fetch", fetch);

    const emoji = new Emoji();

    // Act: execute the code we want to test
    const result = await emoji.get("computer");

    // Assert: check if it worked
    expect(result).toStrictEqual(computer);
  });
});

```

{% endcode %}

{% hint style="info" %}
Structure your tests with 3 well-separated sections: Arrange, Act & Assert.
{% endhint %}

### Containers file

To avoid future mistake, we will create a `containers.ts` file to list all the containers available and their type.

{% code title="containers.ts" %}

```typescript
import fetch from "node-fetch";
import { Container } from "typedi";

enum ContainerNames {
    fetch = 'fetch'
}

export const setFetch = (fn: typeof fetch) => Container.set(ContainerNames.fetch, fn)
export const getFetch = (): typeof fetch => Container.get(ContainerNames.fetch)
```

{% endcode %}

And we can now use it in our `Emoji.ts` class

{% code title="Emoji.ts" %}

```typescript
import { getFetch } from './containers'

class Emoji {
    private readonly _fetch = getFetch()
    async get(emoji: string): Promise<string> {
        const response = await this._fetch("https://api.github.com/emojis");
        const data = await response.json();
        return data[emoji];
    }
}

export { Emoji };
```

{% endcode %}

That's it. We can now mock anything, anywhere in our code.

### Code coverage

If you run `jest index.spec.ts --coverage` you will get a new folder `src/coverage`

Open `coverage/index.html` in your browser.

![](/files/-LndtnnkzTwG018S3nA0)

`Emoji.ts` has 100% code coverage. Let's add a `if` statement and see what happens.

{% code title="Emoji.ts" %}

```typescript
import { getFetch } from './containers'

class Emoji {
    private readonly _fetch = getFetch()
    async get(emoji: string): Promise<string> {
        const response = await this._fetch("https://api.github.com/emojis");
        const data = await response.json();
        if (!data[emoji]) {
            // this is not covered by any test
            return 'emoji not found';
        }
        return data[emoji];
    }
}

export { Emoji };
```

{% endcode %}

Run `jest index.spec.ts --coverage` again

![](/files/-LnducsM6kSjP1xdYgh6)

There are no tests going though this `if` statement.

![](/files/-LnduscYMsa7TBxnGLxu)

This is a fantastic tool to see how much of your code is tested.

{% hint style="info" %}
Add `coverage` to `.gitignore`
{% endhint %}

### Why do we even write test?

Some people struggle to understand why unit tests are useful. So why should we write test?

#### It is documentation

If anything, by reading the list of "it should..." someone can quickly get an understanding of what this class is about. This someone could be you in a few months.

#### If it can break, it will break

Sometimes we get distracted, we make mistakes and we break things. That's why it's important to have a few basic tests to automatically check if everything still does what it's supposed to do.

#### It can be part of the debugging process

Instead of restarting the server, sending a request and looking at the result, you can write a test. You can setup the scenario, call the function and assert the result. It speeds up the feedback loop.

#### Refactoring becomes easier

If you spot something you don't like and want to improve, go ahead. Assuming there's good coverage, if something breaks, tests will catch it.

### To test or not to test

So people believe they must achieve 100% code coverage at all cost. While tests are great most of the time, writing tests for the sake of it is counter-productive.\
Test what makes sense. If something breaks, write a test so it doesn't happen again. With experience, you'll develop a sense for what needs to be tested and what doesn't.

The last thing you want to do is making your codebase more complex to test unimportant parts.

Test and/or mock when it makes sense.

{% hint style="info" %}
[`testing`](https://github.com/florianherrengt/book-code/tree/testing) branch available on GitHub
{% endhint %}
