Electron Adventures: Episode 24: Unit Testing Electron
Tags: electron, javascript, jest, testing
Published: 2021-08-17 01:35:34

In previous episode, `fileInfo` function in `preload.js` got quite long, so let's write some tests.

We can start by installing `jest`, which looks like the most popular backend testing framework for Javascript:

```shell
$ npm install --save-dev jest
```

And we can also put it in `package.json` for convenience:

```javascript
    "test": "jest"
```

### Problem with testing `preload.js`

There are two conventions for organizing tests in Javascript - either moving all specs to separate folder, or putting `__tests__` folders all over the place. I never really understood the appeal of scattering tests files all over, so we'll be doing the tidy thing and have them all in `spec`.

The first thing I tried was just creating `spec/preload.spec.js` and importing `preload.js` from there. Unfortunately that runs into `contextBridge.exposeInMainWorld` not being defined.

### Rewrite `preload.js`

So let's do the easy thing - reduce `preload.js` to just this:

```javascript
let { contextBridge } = require("electron")
let api = require("./preload/api")

contextBridge.exposeInMainWorld("api", api)
```

Which doesn't need any unit tests, as it's just one line of code. It will get covered by integration tests later.

And let's move the rest of the code from `preload.js` to `preload/api.js`, with just this `export`:

```javascript
module.exports = {
  directoryContents,
  currentDirectory,
}
```

### Add tests

I'm not interested in checking low level system calls, so I just created:

* `spec/preload/examples`
* `spec/preload/examples/example_01.md` - small file with 5 bytes
* `spec/preload/examples/example_02.md` - symlink to `example_01.md`
* `spec/preload/examples/example_03` - directory
* `spec/preload/examples/example_03/.gitkeep` - empty file so it actually stays in git, as git does not do empty directories
* `spec/preload/examples/example_04` - link to `example_03`
* `spec/preload/examples/missing` - broken symlink

This is good enough for our test - it won't verify special files, and files deleted while we're running our function, but let's not get too fancy.

Unfortunately we have a few more problems. We don't know what will be the exact `Date` returned, and if we just put the files in the repository, and check them out, there's no guarantee they'll come out with the same last modified date.

So I thought I'd just do something along the lines of:

```javascript
expect(result[0].mtime).toBeInstanceOf(Date)
```

But that returned mysterious:

```
    Expected constructor: Date
    Received constructor: Date
```

Looks like node has multiple `Date` classes? It's very incidental to what we're doing so I didn't investigate it further. If anyone knows the answer, let me know in comments.

### `spec/preload/api.spec.js`

And so here's our spec file:

```javascript
let path = require('path')
let api = require("../../preload/api.js")

test("currentDirectory", () => {
  expect(api.currentDirectory()).toEqual(path.join(__dirname, "../.."))
})

test("directoryContents", async () => {
  let examplesDir = `${__dirname}/examples`
  let result = await api.directoryContents(examplesDir)

  expect(result).toMatchObject([
    {
      linkTarget: null,
      name: "example_01.md",
      mtime: expect.anything(),
      size: 6,
      type: "file",
    },
    {
      linkTarget: "example_01.md",
      name: "example_02.md",
      mtime: expect.anything(),
      size: 6,
      type: "file",
    },
    {
      linkTarget: null,
      name: "example_03",
      mtime: expect.anything(),
      type: "directory",
    },
    {
      linkTarget: "example_03",
      name: "example_04",
      mtime: expect.anything(),
      type: "directory",
    },
    {
      linkTarget: "example_05.md",
      name: "missing",
      type: "broken",
    },
  ])
})
```

Of course we could do a lot more testing, but it's a start.

### How to test backend code

To be honest we shouldn't even be putting that much code in the `preload` - it should just do minimum work to setup some calls to the backend, and the logic should be in the backend.

We can do very similar testing for anything in the backend - just move all complicated logic to separate files, and test those files. They are executed in regular node environment.

Then add some integration tests for the whole thing.

### Results

Here's the results:

![Episode 24 Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dwic23w12e6ly6py7yxh.png)

In the next few episodes, we'll be doing some purely frontend coding for our app.

As usual, [all the code for the episode is here](https://github.com/taw/electron-adventures/tree/master/episode-24-unit-testing-electron).