Testing
@usebutr/testing: fake adapters and fake persistence so unit tests never touch a real wallet or localStorage.
@usebutr/testing provides deterministic test doubles. Install it as a dev
dependency:
npm install --save-dev @usebutr/testingcreateFakeAdapter(options?): WalletAdapter
Every method resolves to a deterministic stub. Override individual methods after construction to inject failure modes.
import { createFakeAdapter } from "@usebutr/testing";
const adapter = createFakeAdapter({ id: "metamask" });
adapter.connect = () => Promise.reject(new Error("user rejected"));FakeAdapterOptions
| Field | Default |
|---|---|
id | "fake" |
name | "Fake Wallet" |
chainPlatform | "evm" |
accounts | [] |
icon | — |
capabilities | all true (merged over your overrides) |
Stub returns include: getAccount() → first account or null; sendTx() →
"0xfakehash"; signMessage(msg) → { signature: msg, signedMessage: msg };
getBalance() → { decimals: 18, formatted: "0", symbol: "ETH" | "SOL", value: 0n };
subscribe() → no-op unsubscribe.
Wire it through createConnector to test the store or React hooks:
const adapter = createFakeAdapter({ id: "fake" });
const config: WalletManagerConfig = {
connectors: [{ id: "fake", name: "Fake", chainPlatform: "evm" }],
createConnector: (id) => (id === "fake" ? adapter : null),
};createFakeConnectedWallet(options?): ConnectedWallet
A pool entry: what usePool, useActiveWallet, and useConnectedWallets hand
your components, and therefore what UI tests render. Accounts are built through
core's buildAccount, so the <chain>:<address> id format stays in one place
instead of being restated in every fixture.
import { createFakeConnectedWallet } from "@usebutr/testing";
const wallet = createFakeConnectedWallet({ chainPlatform: "svm", id: "phantom" });
render(<WalletCard wallet={wallet} />);FakeConnectedWalletOptions
Every FakeAdapterOptions field, plus:
| Field | Default |
|---|---|
chain | the platform's mainnet (eip155:1, solana:mainnet, sui:mainnet, …) |
addresses | one deterministic address for the platform |
adapter | built from the adapter options |
accounts[0] is always account, matching the invariant on ConnectedWallet.
Pass adapter to wrap a connector you already built, in which case the entry
takes its chainPlatform and the adapter-shaping options are ignored:
const adapter = createFakeAdapter({ id: "phantom", chainPlatform: "svm" });
adapter.signMessage = () => Promise.reject(new Error("user rejected"));
const wallet = createFakeConnectedWallet({ adapter });createFakePersistence(seed?): WalletPersistence
In-memory WalletPersistence mirroring WalletStorage's shape: no
localStorage, no cookies. Reads/writes resolve synchronously (wrapped in
Promise.resolve).
import { createFakePersistence } from "@usebutr/testing";
const persistence = createFakePersistence({
activeConnectorId: "fake",
pool: {
fake: {
account: seedAccount,
accounts: [seedAccount],
chainPlatform: "evm",
connectorId: "fake",
name: "Fake Wallet",
},
},
selection: { evm: "fake" },
userDisconnected: true,
});FakePersistenceSeed fields: pool, selection, activeConnectorId,
userDisconnected. Pass it as the storage in
WalletManagerConfig to test hydration and
persistence without side effects. clearAll() resets everything.
Source: packages/testing/src (fake-adapter.ts, fake-persistence.ts, and their
__tests__) in the butr
repository.