samuelcolvin / edge-mock

Tools for testing and developing CloudFlare worker apps.
https://www.npmjs.com/package/edge-mock
MIT License
53 stars 5 forks source link
cloudflare-workers service-worker typescript unit-testing

edge-mock

ci codecov

Tools for developing and testing edge service workers, in particular CloudFlare workers.

edge-mock provides three things:

  1. Implementations for types used in service-workers, e.g. Request, Respones, FetchEvent ReadableStream etc.
  2. A function makeEdgeEnv for installing these types into the global namespace for use in unit tests
  3. A simple HTTP server based on express.js which lets you run your service-worker based app locally for development

You can consider edge-mock as implementing the most commonly used types declare in the @cloudflare/workers-types typescript types package.

While edge-mock is designed to be useful when developing CloudFlare worker applications, it should be usable while developing any service-worker app including for (future) alternative edge worker implementations.

edge-mock is written in TypeScript and while you may be able to use it from vanilla javascript projects, you'd be better off writing your code in TypeScript!

Install

[npm/yarn] add edge-mock

Usage

edge-mock provides the following types (all available to import from edge-mock):

There's also fetch_live (import with import live_fetch from 'edge-mock/live_fetch') which is an implementation of fetch which makes actual http requests using node-fetch. It is installed by default instead of stub_fetch in the dev server, see below.

Please Note: all the above types are designed for use with node while testing and are vanilla in-memory only implementations. They are not designed for production use or with large payloads.

Example of Usage for unit testing

edge-mock works well with jest to make writing unit tests for edge workers delightful.

Let's say you have the following handler.ts with a function handleRequest that you want to test:

export async function handleRequest(event: FetchEvent): Promise<Response> {
  const {request} = event
  const method = request.method
  let body: string | null = null
  if (method == 'POST') {
    body = await request.text()
  }
  const url = new URL(request.url)
  const response_info = {
    method,
    headers: Object.fromEntries(request.headers.entries()),
    searchParams: Object.fromEntries(url.searchParams.entries()),
    body,
  }
  const headers = {'content-type': 'application/json'}
  return new Response(JSON.stringify(response_info, null, 2), {headers})
}

(To see how this would be deployed to cloudflare, see the cloudflare worker TypeScript template)

To test the above handleRequest function, you could use the following:

import {makeEdgeEnv} from 'edge-mock'
import {handleRequest} from '../src/handle.ts'

describe('handleRequest', () => {
  beforeEach(() => {
    makeEdgeEnv()
    jest.resetModules()
  })

  test('post', async () => {
    // Request is available here AND in handleRequest because makeEdgeEnv installed
    // the proxy EdgeRequest into global under that name
    const request = new Request('/?foo=1', {method: 'POST', body: 'hello'})
    // same with FetchEvent, Response etc.
    const event = new FetchEvent('fetch', {request})
    const response = await handleRequest(event)
    expect(response.status).toEqual(200)
    expect(await response.json()).toStrictEqual({
      method: 'POST',
      headers: {accept: '*/*'},
      searchParams: {foo: '1'},
      body: 'hello',
    })
  })
})

Development Server

The development server relies on webpack and uses webpack-watch to reload the server on code changes.

To run the server, add the following to the scripts section of package.json:

  ...
  "scripts": {
    "dev": "edge-mock-server",
    ...
  },
  ...

TODO: explain how edge-mock-config.js works.

You can then run the dev server with:

yarn dev

(or npm run dev if you use npm rather than yarn)

Documentation

KV store

import {EdgeKVNamespace as KVNamespace} from 'edge-mock';
describe('edge-mock', () => {
    test('put and get kv', async() => {
        const kv = new KVNamespace();
        await kv.put('foo','bar');
        const value = await kv.get('foo');
        expect(value).toBe('bar');
    });
});

Wrangler sites integration

TODO

Request Payload

TODO

JSON

TODO

FormData

TODO

Binary Data, ArrayBuffer

TODO

Request.cf

TODO

fetch mocking

TODO