Automated testing with jest
Published: 2021-11-12 16:30:42

## Introduction
Last week, I added static analysis tooling to [cli-ssg](https://github.com/dhillonks/cli-ssg). Continuing the process of managing project complexity, this week I worked on adding automated testing.

There are lots of frameworks to choose from when it comes to automated testing. However, since cli-ssg is built on Node.js, I chose to go with one of the most popular and well supported frameworks, [jest](https://jestjs.io/) to automate the testing process.

## Setting up jest

Starting off by simply installing jest:
```
npm install --save-dev jest
```

Adding a simple `test` script to run `jest` through `npm` in `package.json`:
```
"scripts":{
  "test": "jest"
}
```

Now, I was able to run the tests using `npm test`. I also added a `test-watch` script to have `jest` watch for changes and run tests automatically when the test or source code is updated:
```
"scripts":{
  "test-watch": "jest --watch --"
}
```

With `jest` configured, it was now time to write tests!

## Writing tests:

As a starting point, I started off by testing the `Options` module. More specifically, I decided to test the `validateInput()` function which validates the `this.input` path and returns true if it exists:
```js
validateInput() {
    if (!fs.existsSync(this.input)) {
      throw new Error(
        `${this.input} does not exist. Input path must be a file or directory`
      );
    }
    return true;
  }
```

To test it, I created a `configOptions.js` file and after some back and forth this is what I came up, to test all possible scenarios for this function:
```js
describe('validate input tests', () => {
  let options;

  beforeEach(() => {
    options = new Options();
  });

  test('empty, null and undefined paths should throw error', async () => {
    [null, undefined, ''].forEach((p) => {
      options.input = p;
      expect(() => options.validateInput()).toThrow();
    });
  });

  test('non-existing file should throw error', async () => {
    options.input = 'invalidPath';

    expect(() => options.validateInput()).toThrow(
      'invalidPath does not exist. Input path must be a file or directory'
    );
  });

  test('should validate an existing file', async () => {
    fs.existsSync.mockResolvedValue(true);

    expect(options.validateInput()).toEqual(true);
  });
});

```

With the first unit test down, I wanted to test the core functionality of the SSG. To do this, I narrowed in on the `convertFileToHtml(filePath, stylesheet, lang)` function which converts a `.txt` file and generates `.html` output.

To test this functionality, I ended up using the `beforeEach()` and `afterAll` helpers to create and delete a sample input file using which I could then test this function:
```js
beforeEach(() => {
    fs.writeFileSync(inputFilePath, fileInput);
  });

  afterAll(() => {
    fs.unlinkSync(inputFilePath);
  });
```

The input scenarios that I tested were quite straightforward:
```
- html should be generated
- h1 tag should be generated from txt file
- stylesheet attribute should be rendered as a link tag
- lang attribute should be present in html
```

## Takeaways
With `jest` configured and some tests already written, we can now work on improving the code coverage. It was nice to setup automated testing since without tests, it's easy to break existing code or introduce unexpected bugs. The good news is that now, the tool can be tested by simply running `npm test`:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu9j8xj3xh5arl966qzs.png)

## References
Github repo - https://github.com/dhillonks/cli-ssg
3d3d110 - https://github.com/dhillonks/cli-ssg/commit/3d3d1100350d885a5074a96e9757fa527fcb11c9