How to test Web Workers with Jest
Tags: vue, tutorial, tdd, jest
Published: 2020-10-20 09:05:30

As you could read in Alex's article, [Use Web Workers in your Vue.js Components for Max Performance](https://vuedose.tips/use-web-workers-in-your-vuejs-component-for-max-performance/), you can use Web Workers to maximize performance in your Vue.js app instead of running heavy tasks in the main thread which is UI-blocking. But how can we test Web Workers? Webpack-bundled Web Workers are not supported by Jest so we have to mock the worker in order to test it! Let's see how to do in 3 simple steps, starting from a simple Vue app to calculate the Fibonacci number, where `fibonacci` function is the heavy task performed by the Web worker (you can follow the code [here](https://github.com/astagi/webworker-test))

<img src="https://github.com/astagi/webworker-test/blob/master/public/article/fibonacci_demo.gif?raw=true&v=1" style="width: 300px; height: auto;">

First of all we need to isolate the main functionality of our worker, in this case is really straightforward because it's just our `fibonacci` function (`src/fibonacci.js`)

```js
let fibonacci = (num) => {
  if (num <= 1) return 1;
  return fibonacci(num - 1) + fibonacci(num - 2);
}

export default fibonacci
```

and keep the worker minimal (`src/fibonacci.worker.js`):

```js
import fibonacci from "./fibonacci";

self.onmessage = async function (e) {
  self.postMessage(fibonacci(e.data));
};
```

This way we can mock just the Web Worker part of our implementation (`src/__mocks__/fibonacci.worker.js`)

```js
import fibonacci from "../fibonacci";

export default class fibonacciWorker {
  constructor() {
    // Note that `onmessage` should be overwritten by the code using the worker.
    this.onmessage = () => { };
  }

  postMessage(data) {
    this.onmessage({ data: fibonacci(data) });
  }
}
```

and easily test the main functionality

```js
import { shallowMount } from '@vue/test-utils'
import App from '@/App.vue'

jest.mock("@/fibonacci.worker")

describe('Fibonacci App.vue', () => {
  it('should calculate Fibonacci number', async () => {
    const wrapper = shallowMount(App)
    await wrapper.find('input').setValue('10')
    await wrapper.find('button').trigger('click')
    expect(wrapper.find('.result').element.innerHTML).toBe('Result: 89')
  })
})
```

I created `workerloader-jest-transformer` to generalize this solution so that all workers are mocked at once. This Jest transformer helps you to test Web Workers loaded with Webpack worker-loader module in Jest. It's easy to use, install it with

```sh
yarn add workerloader-jest-transformer --dev
```

and add the tranformation rule to your Jest configuration:

```js
transform: {
  "^.+\\.worker.[t|j]sx?$": "workerloader-jest-transformer"
}
```

This transformer is inspired by [jsdom-worker](https://github.com/developit/jsdom-worker) and implements Web Worker API for JSDOM, so you can remove any mocking code as you can see [here](https://github.com/astagi/webworker-test/tree/feature/jesttransformer).

Workerloader-jest-transformer is highly experimental and code is available on [Github](https://github.com/astagi/workerloader-jest-transformer), any contribution and advice would be greatly appreciated!