sw-test-env
Version:
A sandboxed ServiceWorker environment for testing
121 lines (82 loc) • 5.79 kB
Markdown
[](https://npmjs.org/package/sw-test-env)
[](https://github.com/popeindustries/sw-test-env/actions)
# ServiceWorker Test Environment
A sandboxed `ServiceWorker` context for testing your `ServiceWorker` code on the command line.
Testing code written to run in a `ServiceWorker` is hard, and generally requires a browser environment and lots of ceremony to work. `sw-test-env` is the magic ingredient for easy unit/integration testing of `ServiceWorker` code. Just load your script, and poke, prod, inspect, and manipulate the `ServiceWorker` context:
```js
import assert from 'assert';
import { connect } from 'sw-test-env';
// Equivalent to opening a browser window and accessing window.navigator.serviceWorker
const sw = connect('http://localhost:3000', 'path/to/webroot');
async function test() {
// Load and execute sw.js in a sandboxed ServiceWorker context
const registration = await sw.register('sw.js');
// Trigger the 'install' event
await sw.trigger('install');
// Inspect the cache contents by reading from the installing service worker's internal scope
const cache = await sw.__serviceWorker__.self.caches.open('v1');
const requests = await cache.keys();
const urls = requests.map((request) => request.url);
assert.ok(urls.includes('assets/index.js'));
}
```
## Features
- load and execute `ServiceWorker` script files in a sandboxed context
- inspect the properties of the `ServiceWorker` scope (`clients`, `caches`, `registration`, variables, etc)
- manually trigger events on `ServiceWorker` (`install`, `activate`, `fetch`, `error`, etc)
- connect multiple clients
- register multiple, scoped `ServiceWorker` instances
- `postMessage` between clients and registered `ServiceWorker` instances
- use `indexedDB`
- TODO: register for notifications and push messages to connected clients
## Caveats
- limited `Response` streaming and body conversion (uses the primitives from [node-fetch](https://github.com/bitinn/node-fetch))
- `fetch` calls will be executed, so a request mocking tool like [nock](https://github.com/node-nock/nock) is recommended
- `importScripts()` in service worker files not supported (use `import` statements instead)
- requires at least version 16 of Node
- not yet possible to cache based on `VARY` header
- not tested against spec test suite or specific browser behaviour
## API
#### **`connect(url: string, webroot: string): Promise<MockServiceWorkerContainer>`**
Create a new `MockServiceWorkerContainer` instance at `url` (default is `http://localhost:3333/`) with `webroot` (default is current working directory). This is equivalent to opening a browser at `url` and accessing the `window.navigator.serviceworker` object. See [MockServiceWorkerContainer](#mockserviceworkercontainer) below for additional behaviour.
Multiple connections to same/different origins are supported, with access to `MockServiceWorker` instances determined by `scope`.
**Note**: the `webroot` argument is used to resolve the path for registering the `MockServiceWorker`.
#### **`destroy(): Promise<void>`**
Destroy all active `MockServiceWorkerContainer` instances and their registered `MockServiceWorker` instances. Should generally be called after each test (for example, in `afterEach()` when using Mocha/Jest/etc).
#### **`Headers, MessageChannel, Request, Response`**
Classes for creating instances of `Headers`, `MessageChannel`, `Request`, and `Response` to be used when interacting with the `MockServiceWorker` context.
### MockServiceWorkerContainer
In addition to the behaviour documented [here](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer), a `MockServiceWorkerContainer` instance returned by `connect()` has the following additions:
#### **`register(scriptURL: String, options: { scope: string }): Promise<MockServiceWorkerRegistration>`**
Load and execute `scriptURL` in a `MockServiceWorker` context. `scriptURL` may be a relative or absolute filepath.
**`options`** include:
- **`scope: String`** the `MockServiceWorker` registration scope (defaults to `./`). Multiple `MockServiceWorker` instances can be registered on the same origin with different scopes.
#### **`ready: Promise<void>`**
Force registered script to `install` and `activate`:
```js
const registration = await sw.register('sw.js');
await sw.ready;
assert.equal(sw.controller.state, 'activated');
```
#### **`trigger(eventType: 'install' | 'activate'): Promise<void>`**
#### **`trigger(eventType: 'fetch', options: FetchEventInit): Promise<Response>`**
#### **`trigger(eventType: 'error' | 'unhandledrejection', error: Error): Promise<void>`**
Manually trigger an event in the `MockServiceWorker` scope:
```js
const registration = await sw.register('sw.js');
await sw.ready;
const response = await sw.trigger('fetch', { request: '/assets/index.js' });
assert.equal(response.url, 'http://localhost:3333/assets/index.js');
```
#### **`__serviceWorker__: MockServiceWorker`**
Access the registered `MockServiceWorker`, including it's internal `self` scope:
```js
const registration = await sw.register('sw.js');
await sw.ready;
const cache = sw.__serviceWorker__.self.caches.open('v1');
const requests = await cache.keys();
const urls = requests.map((request) => request.url);
assert.ok(urls.includes('assets/index.js'));
```
## Inspiration & Thanks
Special thanks goes to Pinterest ([service-worker-mock](https://github.com/pinterest/service-workers/tree/master/packages/service-worker-mock)) and Nolan Lawson ([pseudo-worker](https://github.com/nolanlawson/pseudo-worker)) for their ideas (some of which were borrowed here) and inspiring work.