Skip to main content

TaxJar Mock Harness

The TaxJar mock harness is a local, self-contained replacement for api.taxjar.com. It lets tests drive the full tax-bearing order flow without a live TaxJar account or internet access.

It implements two TaxJar v2 endpoints:

  • POST /v2/taxes — returns deterministic tax based on a hard-coded US state rate table and a configurable nexus list.
  • GET /v2/rates/:zip — returns the rate for a state identified by the ?state= query parameter.

Everything lives under devtools/taxjar-mock/. Nothing in src/** depends on it; the real TaxJar adapter points at it via the TAXJAR_API_BASE env var.

Quick start (manual exploration)

# Boot the mock on port 4004 (default)
pnpm taxjar-mock

# In another shell, hit it directly
curl -s -X POST http://localhost:4004/v2/taxes \
-H 'Content-Type: application/json' \
-d '{"to_state":"NY","amount":50,"shipping":5}' | jq .

The mock accepts any non-empty Authorization: Bearer <token> header — no real API key is needed.

Rate table

StateRate
NY8.875%
CA7.25%
TX6.25%
FL6.00%
IL6.25%
PA6.00%
OH5.75%
NJ6.625%
GA4.00%
NC4.75%

Any state not in the table returns a 0% rate. Nexus is simulated via the nexusStates constructor option — states not in the list always return 0 tax regardless of rate.

Wiring in a test

import { startTaxJarMockServer } from '../devtools/taxjar-mock/server.js'
import { createTaxJarAdapter } from '../src/adapters/taxjar.adapter.js'

const mock = await startTaxJarMockServer({ port: 0, nexusStates: ['NY'] })

const adapter = createTaxJarAdapter({
apiKey: 'test-key', // any non-empty string
logger,
baseUrl: mock.baseUrl
})

// ...run test...

await mock.stop()

Configuration

OptionDescriptionDefault
portPort to listen on. 0 = OS-assigned ephemeral.4004
nexusStatesArray of 2-letter state codes the tenant has nexus in.all 10

Env var TAXJAR_MOCK_PORT overrides the default when using pnpm taxjar-mock.

Default port

4004. Configurable via TAXJAR_MOCK_PORT env var or the port option passed to startTaxJarMockServer(). Integration specs always pass port: 0 to get a random ephemeral port, avoiding conflicts in parallel test runs.