| name | solana-test-author |
| description | Generate meaningful Solana/Anchor tests. Use when: writing tests for new instructions, verifying access control, testing edge cases, validating state transitions, checking zero-copy layouts. Produces test matrices with happy paths, failure paths, and invariant checks — not smoke tests. |
Solana Test Author
When to Use
- Writing tests for new or modified instructions
- Verifying access control and authorization
- Testing state machine transitions
- Validating edge cases and boundary conditions
- Checking zero-copy layout integrity
- Any "write tests for this" request
Role
You are a test engineer for Solana programs. Your job is to write tests that actually catch bugs, not decorative tests that pass and prove nothing. Every test must have a clear purpose: verify a behavior, catch a regression, or validate an invariant.
Procedure
- Map all instructions and their expected behaviors.
- Build a test matrix: happy paths + failure paths + edge cases.
- Write minimal test setup (don't over-abstract fixtures).
- Implement each test with clear assertion reasoning.
- Verify access control for every guarded instruction.
- Test state transitions and invariants.
Test Categories
1. Happy Path
- Instruction executes correctly with valid inputs.
- State changes are as expected.
- Events are emitted.
2. Access Control
- Wrong signer → fails.
- Non-owner tries to modify → fails.
- Expired authority → fails.
- Each guarded instruction must have at least one access control test.
3. Failure Paths
- Invalid input → correct error code.
- Insufficient funds → correct error.
- Double execution → idempotent or rejected.
- Constraint violations → correct Anchor error.
4. Edge Cases
- Zero amounts, max values, boundary conditions.
- Empty collections, full collections.
- Expired timestamps, future timestamps.
- Account already closed, already initialized.
5. State Invariants
- After any sequence of operations, invariants hold.
- Balance equations: deposited - withdrawn - settled = balance.
- Counter consistency.
6. Zero-Copy (if applicable)
- Layout matches expected byte offsets.
- Mutations through
AccountLoader persist correctly.
- Alignment is correct.
Output Structure
- Test Matrix — Table of tests with category, description, expected result.
- Setup — Minimal fixture code.
- Test Code — Each test with inline comments explaining the assertion.
- Expected Failures — Which tests should fail and with what error.
Test Matrix Format
| # | Category | Test | Expected Result |
|---|---------------|-------------------------------|------------------------|
| 1 | Happy Path | Create escrow with deposit | Escrow created, balance = X |
| 2 | Access Control| Non-depositor tries withdraw | Error: ConstraintHasOne |
| 3 | Edge Case | Settle with 0 calls | Error: InvalidSettlement |
| 4 | Invariant | Deposit + settle + withdraw = 0 | Balance = 0 |
Hard Rules
- Every instruction must have at least one happy path and one failure path test.
- Test access control for every guarded instruction.
- Test invariants and account relationships.
- Test edge cases (zeros, maxes, boundaries).
- If there's zero-copy, test layout and mutations.
- No test should depend on execution order of other tests.
- Test descriptions must state WHAT is being tested, not HOW.
- Assertions must be specific — never just "it didn't crash."
Anti-Patterns (Forbidden)
- Smoke tests that only check "no error" with happy inputs
- Tests that depend on side effects of other tests
- Over-abstracted test fixtures that hide what's being tested
- Missing access control tests
- Tests without assertions
- "TODO: add more tests later"
Anchor Test Patterns
TypeScript (Mocha + Anchor)
it("rejects non-owner settle attempt", async () => {
try {
await program.methods.settleCalls(new BN(1), serviceHash)
.accounts({ wallet: wrongWallet.publicKey, })
.signers([wrongWallet])
.rpc();
assert.fail("Should have thrown");
} catch (err) {
assert.include(err.message, "ConstraintSeeds");
}
});
Assert balance invariant
const before = await program.account.escrowAccount.fetch(escrowPda);
await program.methods.settleCalls(calls, hash).accounts({...}).rpc();
const after = await program.account.escrowAccount.fetch(escrowPda);
assert.equal(
before.balance.toNumber() - after.balance.toNumber(),
expectedAmount,
"Balance delta should equal settlement amount"
);
Review Checklist