Skip to content

Testing Arcjet

Unlike network-based security solutions, Arcjet can run locally, in staging, and in production. This helps you test your security rules before deploying them.

You can also automate testing against your Arcjet-protected routes using standard testing libraries.

If you are testing AI tool calls or other non-HTTP actions, use the Agent guards test matrix and the in-memory test client. Guard tests must assert that a denied or unavailable decision prevents the side effect, not only that Arcjet returned a particular result. Capture tests record that an allowed action happened, so they must assert that the application recorded the expected action after the side effect ran. See Capture events.

Newman is an open source command-line collection runner for Postman. It lets you define requests in Postman format and run them from the command line without requiring Postman itself.

You can find our full example of how to test Arcjet with Express and Newman on GitHub. The key files are:

The example README explains how to start the test application and then execute the tests using Newman.

To adapt these to your own application you would modify the tests/*.json files to point to your application endpoints, and then run them using the Newman CLI. This lets you automate testing and run multiple iterations, for example to test a rate limit.

Arcjet runs the same in production as it does locally, so the behavior you see in development matches production. You can trigger each rule in the following ways:

  • Shield: Send 5 requests with the special header x-arcjet-suspicious: true to trigger the shield rule on the next request.
  • Rate limiting: Make more requests than the rate limit allows.
  • Bot protection: Bot detection uses multiple heuristics, so the most reliable way to get a bot detection DENY response is to make a request that is always considered a bot. With a deny rule set to CURL you get a DENY response when you make a request using the curl command. For more information, see Identifying bots.
  • Email validation: Use an email address that has invalid syntax or does not match any other rules you have configured, such as an address with no MX records or one from a disposable email service.

HTTP collection runners cannot reach guard() or capture(). Register an in-memory test client so that you can assert against application code that imports the free functions without talking to Arcjet.

Passing a client explicitly is the recommended production path. launchArcjet() / launch_arcjet() never registers one. Registration is a separate call for code too deep to receive a handle.

Free guard() fail-opens if no client is registered. It returns ALLOW with an error result, so hasFailedOpen() / has_failed_open() is true. That is “policy did not run”, not a pass. Free capture() drops the event silently.

import { registerTestClient } from "@arcjet/guard/testing";
import { refund } from "./refund.ts";
test("refund captures an event", async () => {
using arcjet = registerTestClient();
await refund("inv_1");
assert.equal(arcjet.captures[0]?.action, "refund.issued");
});

using unregisters at the end of the block, including when the test fails. The using syntax needs Node.js 24 or a TypeScript compile step. On Node.js 22, call unregister() from a finally:

test("refund captures an event", async () => {
const arcjet = registerTestClient();
try {
await refund("inv_1");
assert.equal(arcjet.captures[0]?.action, "refund.issued");
} finally {
arcjet.unregister();
}
});

registerTestClient() throws if a client is already registered, which surfaces a leak from an earlier test. Each recorded capture goes through the same validation as a real capture(), so a call the real client would drop is not recorded. Recording is synchronous – no flush() is required.

guard() on the test client records the call and returns a fail-open ALLOW, because no rule ran. It does not stub per-rule verdicts. Helpers that fail closed on a failed-open decision – guardTool, guardAction – therefore deny against this client.

To register a real client in production:

import { launchArcjet, registerArcjet } from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
registerArcjet(arcjet);

Free guard(), capture(), and flush() then reach that client. A second registration does not displace the first.

For registration edge cases and the policy-denial matrix, see Agent guards testing and reference.

To write a sampling function that tests your Arcjet security rules on a subset of your traffic, see the sampling blueprint.

Discussion