Test, observe, and operate KV

Test the missing and stale paths

Cover null, expired, malformed, and stale values instead of testing only a fresh successful read.

Most KV tests I see check one thing: write a value, read it back, done. That’s the path that never breaks in production. The paths that break are the missing key, the expired key, the malformed value, and the value that is 40 seconds old in another region.

Test those.

Use a real binding, not a mock

The Workers Vitest integration runs your tests inside the Workers runtime, with real local bindings. Install @cloudflare/vitest-pool-workers, point it at your wrangler.jsonc, and import env from cloudflare:test:

import { env } from 'cloudflare:test'
import { it, expect, beforeEach } from 'vitest'

beforeEach(async () => {
  await env.FLAGS.delete('checkout_enabled')
})

it('treats a missing flag as disabled', async () => {
  const value = await env.FLAGS.get('checkout_enabled')
  expect(value === 'true').toBe(false)
})

env.FLAGS here is the same KV API your Worker uses, backed by local storage. No mock to keep in sync with the real behavior.

Seed each test on its own and clean up by key or by prefix in beforeEach. Tests that depend on leftovers from a previous test pass alone and fail in a different order.

Write the fallback first

Write the fallback before testing the happy path:

const value = await env.FLAGS.get('checkout_enabled')
const checkoutEnabled = value === 'true'

Test true, false, a missing key, and a recently changed value from two locations. Your code should not turn a missing value into accidental access. The multi-location test makes eventual consistency visible instead of leaving it as an abstract warning.

Notice the comparison. value === 'true' turns null, 'false', and garbage all into false. A check like if (value) would treat the string 'false' as enabled, because a non-empty string is truthy. That’s the kind of bug a missing-key test catches in seconds.

Malformed values

Store broken JSON under a key on purpose and read it with the json type:

await env.FLAGS.put('pricing', '{not json')
await expect(env.FLAGS.get('pricing', 'json')).rejects.toThrow()

Now you know what happens when someone edits a value by hand in the dashboard. Decide whether your Worker should fall back to defaults or return a 500, and write that test too.

Stale values

You can’t force global propagation inside a unit test. But you can verify the product stays correct when handed an older value. Write the value your code would have read 60 seconds ago, run the decision, and assert the outcome is still safe.

Try this: write a table with four rows, fresh, stale, missing, and malformed configuration. For each one, write down the expected behavior, then turn each row into one test.

Lesson completed