Testing WorkOS in CI/CD: A practical guide
A hands on walkthrough for seeding data, testing full login flows, and catching failure paths in your pipeline, all without a single network call to the live WorkOS API.
If you've ever tried to test an authentication flow, you've probably run into the same wall: hitting a live API from your test suite is slow and flaky, but hand rolling mocks for JWTs, webhooks, and SSO redirects is its own maintenance job. Every time your auth provider changes a response shape, your mocks drift out of sync, and you end up debugging your test double instead of your app.
@workos/emulate is a local, in memory WorkOS API server built specifically to remove that trade off. It speaks the real WorkOS API surface, so your SDK code doesn't know the difference between talking to the emulator and talking to production. This guide walks through setting it up, seeding realistic data, testing a full login flow end to end, and wiring it into CI so your pipeline never needs network access to WorkOS at all.
Why not just mock the SDK
Mocking the WorkOS SDK directly usually starts small: stub out create_user, stub out authenticate, move on. But auth flows are rarely one call. A real login touches an authorize redirect, a token exchange, a webhook delivery, and sometimes MFA or SSO on top of that. Each of those needs its own mock, and each mock needs to stay accurate as the SDK evolves.
The emulator sidesteps this by being a real HTTP server. Your code makes real requests and gets real, spec shaped responses back, including signed webhooks and valid JWTs. You get the speed and isolation of a mock without maintaining one.
Quickstart
Install the emulator as a dev dependency:
Start it from the command line:
By default it listens on http://localhost:4100 and accepts the API key sk_test_default. Point any WorkOS SDK, in any language, at that base URL instead of https://api.workos.com, and use the test API key. That's the entire integration: no code changes beyond configuration.
Check that it's up with a health check, which is handy for CI:
Seeding realistic data
Tests are only as good as their fixtures. Rather than creating users and organizations through a setup script before every run, you can seed the emulator from a config file so every test starts from a known, reproducible state.
Create workos-emulate.config.yaml:
Then start the emulator with that seed:
You can seed a lot more than users and orgs, including SSO connections with directory profiles, machine to machine applications with pinned client credentials, and API keys. That's useful for tests that need a fully formed tenant (an org, a couple of users, a role structure, and an active SSO connection) without a mountain of setup code in every test file.
Testing your login flow end to end
The emulator implements the full AuthKit login story, so you can run your app's actual login flow against it: hosted authorize, callback, token exchange, and webhook handling. Nothing about your app's auth code needs to be different for tests.
By default, the authorize endpoint auto redirects with a code, which is convenient for API level tests but not useful for a browser test that needs something to click. Pass --interactive to get a real login page instead:
Here's what that looks like in a Playwright test:
This is also what makes the emulator useful for agent driven browser tests, since there's an actual page to interact with rather than an instant redirect. No dashboard login, no test identity provider, and it works in headless mode.
Verifying webhooks
Every resource creation and authentication outcome in the emulator fires a signed webhook, using the same event names and payload shapes as production. That means you can test webhook handling as part of your regular suite instead of treating it as a manual, staging only step.
Register an endpoint at startup by adding it to your seed file:
An empty events list subscribes to everything. Webhooks are signed the same way production WorkOS signs them, with a WorkOS-Signature header containing a timestamp and an HMAC. If your app already uses the official SDK's webhooks.constructEvent helper to verify signatures, it will verify emulator webhooks without any changes.
A typical test pattern is to spin up a small receiver in your test process, register it as the webhook endpoint, trigger an action (creating a user, for example), and then poll the receiver for the expected event rather than asserting on it immediately, since delivery is asynchronous.
Testing failure paths with error hooks
Happy path tests are easy. The harder, more valuable tests are the ones that check what your app does when WorkOS returns a 422, a 500, or a rate limit. Error hooks let you force specific responses on demand, without touching your application code.
You can add hooks at runtime over HTTP:
Or manage them programmatically if you're using the emulator's Node API:
This is the kind of test that almost never gets written against a live API, since you can't reliably make a production service return a 503 on command. Against the emulator it's a couple of lines, which makes it much more likely your retry logic actually gets exercised before a customer finds the gap.
Wiring it into CI
None of the above is useful in CI unless the emulator is easy to start and stop as part of the pipeline. Since it's a plain HTTP server with no external dependencies, that's straightforward. Here's a GitHub Actions example that starts the emulator in the background, waits for it to be healthy, runs the test suite, and tears it down:
The same shape works in any CI provider that lets you run a background process and a health check before your test step. Since there's no live API involved, this runs the same way on a laptop, in a pull request, and on a scheduled nightly build, with no shared account, no rate limits, and no risk of test data leaking into a real WorkOS environment.
Testing service to service auth
If part of your system uses WorkOS Connect applications for machine to machine authentication, you can seed those too, with a pinned client ID and secret so a service has known credentials before it ever talks to your dashboard:
A test (or the service itself, in a local dev environment) can then exchange those credentials for a scoped access token exactly as it would in production:
The token you get back is a real, JWKS verifiable JWT, so if your service validates tokens with a library like jose, it validates emulator tokens with no special casing.
A note on what the emulator is not for
The emulator stores everything in memory, has no real authentication of its own, and isn't meant to be reachable outside your local machine or CI runner. Treat it the way you'd treat any test double: great for development and automated testing, not something to expose on a shared network or use as a substitute for a real WorkOS environment in staging or production.
Next steps
This covers the core workflow: install, seed, run your app's real login flow against a local server, test failure paths on demand, and run all of it in CI without touching the network. From here, the emulator's README is the best reference for the full seed file schema, the complete list of emitted webhook events, and language specific examples beyond Node (Python, PHP, and anything else with a WorkOS SDK follow the same pattern: point the base URL at the emulator and use the test API key).
If you build something with this guide, or hit a gap in what the emulator covers, opening an issue on the repo is the fastest way to get it fixed.