# Testing

To make testing feel like writing TypeScript scripts — not standing up infrastructure — we built [`@deployoor/testing`](/packages) on top of [EDR](https://github.com/NomicFoundation/edr), the Rust EVM behind Hardhat 3.

`createTestClients()` gives you an **in-memory EVM** and viem clients wired to it. Your generated `getOrDeploy` functions work unchanged: spread `clients`, deploy, call `contract.read` / `contract.write`. No local node, no `deployments/` writes on disk.

## Quick example

```bash
pnpm add -D @deployoor/testing vitest
```

Put your project's compile in front of the test script. A generated deployer holds a name, a fully-qualified name and an abi, and reads bytecode from your artifacts when you call it, so a test run against an uncompiled tree fails — clearly, but it fails:

```json
{
  "scripts": {
    "test": "hardhat compile && vitest run"
  }
}
```

That is the Hardhat form. Foundry projects use `forge build && vitest run`. A plain-Solidity project needs no separate compile step — deployoor compiles those sources itself when it reads them — but it does need a compiler on hand: `pnpm add -D @tevm/compiler solc`.

```ts
import { test, expect } from "vitest";
import { createTestClients } from "@deployoor/testing";
import { getOrDeployToken } from "../deployers";

test("transfer moves the balance", async () => {
  const clients = await createTestClients();
  const [deployer, bob] = clients.accounts;

  const { contract: token } = await getOrDeployToken({
    ...clients,
    args: [deployer.address],
  });

  await token.write.transfer([bob.address, 1000n]);
  expect(await token.read.balanceOf([bob.address])).toBe(1000n);
});
```

## Parallel tests, full hardware

Each `createTestClients()` call gets its own isolated in-memory chain. Spin up as many instances as you want — run test files or cases **in parallel** and use all your CPU cores without port conflicts or shared chain state.

```ts
// vitest.config.ts — run files in parallel; each test file can call createTestClients()
export default defineConfig({
  test: { pool: "threads" },
});
```

No anvil. No `--fork-url`. No serial "one chain per machine" bottleneck.

## Multiple signers

```ts
const clients = await createTestClients();
const alice = clients.walletClientFor(clients.accounts[1]);
```

## Disable plugins in tests

If your config has verifier or notifier plugins:

```ts
const clients = await createTestClients();
const [owner] = clients.accounts;

await getOrDeployToken({
  ...clients,
  args: [owner.address],
  plugins: { etherscan: false, slack: false },
});
```

## Forking

```ts
const clients = await createTestClients({
  fork: { url: process.env.MAINNET_RPC, blockNumber: 21_000_000n },
});
```

Your prefunded accounts stay funded, and real remote state is readable through the same viem clients. Add `deployments` + `deploymentNetwork` to seed committed records onto the fork so `getOrDeploy` reuses production addresses instead of redeploying.

Separately, `deployoor generate` can compile plain-Solidity projects with no Hardhat or Foundry, via the optional peer dependencies `@tevm/compiler` and `solc` — see the [tevm guide](/guides/tevm). Those are a compiler, not the test EVM, and installing them is opt-in.

## Requirements

`@deployoor/testing` requires **Node ≥ 22** (EDR dependency).

Your contracts must be **compiled**, because `createTestClients()` replaces the chain and the store, not the artifacts. For Hardhat and Foundry that means running `hardhat compile` or `forge build` first, and test deploys then read bytecode, compiler version and sources from `artifacts/` (or `out/`) exactly as a real deploy does. For a plain-Solidity project deployoor compiles the sources itself, so there is no separate command — only `@tevm/compiler` and `solc` to install. A test that builds a `TypedArtifact` by hand is the one exception either way: deployoor uses that artifact exactly as supplied and never reads the filesystem for it.
