testing

Instructions for writing and organizing Foundry tests in the RuleEngine project

6 stars

Best use case

testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Instructions for writing and organizing Foundry tests in the RuleEngine project

Teams using testing should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/testing/SKILL.md --create-dirs "https://raw.githubusercontent.com/CMTA/RuleEngine/main/.claude/skills/testing/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/testing/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How testing Compares

Feature / AgenttestingStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Instructions for writing and organizing Foundry tests in the RuleEngine project

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

This file provides project-specific test conventions, organization, and patterns for the RuleEngine codebase. For generic Foundry test cheatcodes, assertions, and deployment scripts, see the `foundry` skill.

## Test Directory Structure

```
test/
├── HelperContract.sol             # Base helper for RuleEngine (RBAC) tests
├── HelperContractOwnable.sol      # Base helper for RuleEngineOwnable tests
├── utils/
│   ├── CMTATDeployment.sol        # CMTAT deployment helper
│   └── CMTATDeploymentV3.sol      # CMTAT v3 deployment helper
├── RuleEngine/                    # Tests for RuleEngine (RBAC variant)
│   ├── AccessControl/
│   ├── RulesManagementModuleTest/
│   ├── ruleEngineValidation/
│   ├── RuleEngineDeployment.t.sol
│   ├── RuleEngineCoverage.t.sol
│   ├── ERC3643Compliance.t.sol
│   └── IRuleInterfaceId.t.sol
├── RuleEngineOwnable/             # Tests for RuleEngineOwnable (ERC-173 variant)
│   ├── AccessControl/
│   ├── RulesManagementModuleTest/
│   ├── RuleEngineOwnableDeployment.t.sol
│   ├── RuleEngineOwnableCoverage.t.sol
│   └── ERC3643Compliance.t.sol
└── RuleWhitelist/                 # Tests for the whitelist mock rule
    ├── AccessControl/
    ├── RuleWhitelist.t.sol
    └── CMTATIntegration.t.sol
```

### Organization Rules

- Tests for `RuleEngine` go in `test/RuleEngine/`
- Tests for `RuleEngineOwnable` go in `test/RuleEngineOwnable/`
- Shared test logic uses `*Base.sol` abstract contracts with virtual hooks
- Test files use `.t.sol` suffix; base/helper files use `.sol`
- **Both variants must be tested.** When adding a feature to `RuleEngineBase`, add tests in both `test/RuleEngine/` and `test/RuleEngineOwnable/`.

## Helper Contracts

### HelperContract (for RuleEngine tests)

```solidity
import "./HelperContract.sol";

contract MyTest is Test, HelperContract {
    // ...
}
```

- Inherits all invariant storage contracts (gives access to error `.selector` values)
- Admin address: `DEFAULT_ADMIN_ADDRESS = address(1)`
- Instantiates: `ruleEngineMock` (`RuleEngine`), `ruleWhitelist`, `ruleConditionalTransferLight`
- Provides result variables: `resUint256`, `resUint8`, `resBool`, `resString`, `resAddr`

### HelperContractOwnable (for RuleEngineOwnable tests)

```solidity
import "../HelperContractOwnable.sol";

contract MyOwnableTest is Test, HelperContractOwnable {
    // ...
}
```

- Same structure as `HelperContract` but for the Ownable variant
- Owner address: `OWNER_ADDRESS = address(1)`
- Instantiates: `ruleEngineMock` (`RuleEngineOwnable`)
- Additional address: `NEW_OWNER_ADDRESS = address(8)` for ownership transfer tests

## Test Addresses

| Name | Address | Purpose |
|------|---------|---------|
| `DEFAULT_ADMIN_ADDRESS` / `OWNER_ADDRESS` | `address(1)` | Admin/owner for RuleEngine / RuleEngineOwnable |
| `WHITELIST_OPERATOR_ADDRESS` | `address(2)` | Whitelist rule operator |
| `RULE_ENGINE_OPERATOR_ADDRESS` | `address(3)` | RuleEngine operator |
| `ATTACKER` | `address(4)` | Unauthorized caller |
| `ADDRESS1` | `address(5)` | Test user 1 |
| `ADDRESS2` | `address(6)` | Test user 2 |
| `ADDRESS3` | `address(7)` | Test user 3 |
| `NEW_OWNER_ADDRESS` | `address(8)` | Ownership transfer target (Ownable only) |
| `CONDITIONAL_TRANSFER_OPERATOR_ADDRESS` | `address(9)` | Conditional transfer operator |

## Naming Conventions

Test function names use camelCase with `test` prefix:

```
testCan<Action>()                 — positive test (should succeed)
testCannot<Action>()              — negative test (expects revert)
testCannotAddEOAAsRule()          — specific error case
testSupportsRuleEngineInterface() — ERC-165 interface check
```

Examples from the codebase:
- `testCanSetRules()` — setting rules succeeds
- `testCannotSetRuleIfARuleIsAlreadyPresent()` — duplicate rule reverts
- `testCannotSetEmptyRulesT1WithEmptyTab()` — edge case with variant identifier
- `testMsgDataReturnsCalldata()` — coverage test for internal function

## Revert Testing

**Always use specific error selectors**, never bare `vm.expectRevert()`:

```solidity
// Good — specific error selector
vm.expectRevert(RuleEngine_RuleInvalidInterface.selector);
ruleEngineMock.addRule(IRule(address(0x999)));

// Bad — matches any revert
vm.expectRevert();
ruleEngineMock.addRule(IRule(address(0x999)));
```

Error selectors are available because test helpers inherit the invariant storage contracts (`RuleEngineInvariantStorage`, `RulesManagementModuleInvariantStorage`, etc.).

## Base Test Pattern (Shared Logic)

For functionality shared across `RuleEngine` and `RuleEngineOwnable`, use abstract base contracts with virtual hooks:

```solidity
// Base contract defines shared tests + virtual hook
abstract contract CMTATIntegrationBase is Test, HelperContract {
    function _deployCMTAT() internal virtual;

    function setUp() public {
        _deployCMTAT();
        // shared setup: deploy RuleEngine, bind token, add rules, etc.
    }

    function testCanTransferWithWhitelist() public {
        // shared test logic
    }
}

// Concrete test implements the hook
contract CMTATIntegration is CMTATIntegrationBase {
    function _deployCMTAT() internal override {
        // deploy specific CMTAT version
    }
}

// Another concrete test for a different CMTAT version
contract CMTATIntegrationV3 is CMTATIntegrationBase {
    function _deployCMTAT() internal override {
        // deploy CMTAT v3
    }
}
```

This pattern is used for:
- `CMTATIntegrationBase` — CMTAT integration tests across CMTAT versions
- `RuleEngineOperationRevertBase` — revert scenario tests

## Test Template: RuleEngine (RBAC)

```solidity
//SPDX-License-Identifier: MPL-2.0
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../HelperContract.sol";

contract MyFeatureTest is Test, HelperContract {
    RuleEngine ruleEngineMock;
    RuleWhitelist ruleWhitelist;

    function setUp() public {
        ruleWhitelist = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, ZERO_ADDRESS);
        ruleEngineMock = new RuleEngine(
            DEFAULT_ADMIN_ADDRESS,
            ZERO_ADDRESS,  // forwarder
            ZERO_ADDRESS   // token (bound later)
        );
    }

    function testCanDoSomething() public {
        vm.prank(DEFAULT_ADMIN_ADDRESS);
        ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
        resUint256 = ruleEngineMock.rulesCount();
        assertEq(resUint256, 1);
    }

    function testCannotDoSomethingUnauthorized() public {
        vm.prank(ATTACKER);
        vm.expectRevert();  // AccessControl revert
        ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
    }
}
```

## Test Template: RuleEngineOwnable

```solidity
//SPDX-License-Identifier: MPL-2.0
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../HelperContractOwnable.sol";

contract MyFeatureOwnableTest is Test, HelperContractOwnable {
    RuleEngineOwnable ruleEngineMock;
    RuleWhitelist ruleWhitelist;

    function setUp() public {
        ruleWhitelist = new RuleWhitelist(OWNER_ADDRESS, ZERO_ADDRESS);
        ruleEngineMock = new RuleEngineOwnable(
            OWNER_ADDRESS,
            ZERO_ADDRESS,  // forwarder
            ZERO_ADDRESS   // token
        );
    }

    function testCanDoSomething() public {
        vm.prank(OWNER_ADDRESS);
        ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
        resUint256 = ruleEngineMock.rulesCount();
        assertEq(resUint256, 1);
    }

    function testCannotDoSomethingUnauthorized() public {
        vm.prank(ATTACKER);
        vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", ATTACKER));
        ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
    }
}
```

## Coverage Tests

Coverage tests live in `RuleEngineCoverage.t.sol` / `RuleEngineOwnableCoverage.t.sol` and target hard-to-reach code paths:

- `supportsInterface()` for all supported interface IDs
- `_msgData()` via exposed mock contracts (`RuleEngineExposed`, `RuleEngineOwnableExposed`)
- AccessControl fallback paths in `supportsInterface`
- ERC-165 rejection of invalid rules (EOA, wrong interface)

Exposed mock contracts are in `src/mocks/RuleEngineExposed.sol` and expose internal functions for testing.

## Running Tests

```bash
forge test                                    # All tests
forge test --match-contract RuleEngineCoverage  # Specific test contract
forge test --match-test testCanSetRules         # Specific test function
forge test -vvv                                 # Verbose (shows revert reasons)
forge test --gas-report                         # Gas usage report
forge coverage                                  # Code coverage
```

Related Skills

cmta

6
from CMTA/RuleEngine

main concept behind cmta

foundry

6
from CMTA/RuleEngine

Instructions for Foundry Development (test & deployment script)

erc173-ownership

6
from CMTA/RuleEngine

ERC-173-A standard interface for ownership of contracts

swift-protocol-di-testing

144923
from affaan-m/everything-claude-code

基于协议的依赖注入,用于可测试的Swift代码——使用聚焦协议和Swift Testing模拟文件系统、网络和外部API。

DevelopmentClaude

perl-testing

144923
from affaan-m/everything-claude-code

使用Test2::V0、Test::More、prove runner、模拟、Devel::Cover覆盖率和TDD方法的Perl测试模式。

DevelopmentClaude

ai-regression-testing

144923
from affaan-m/everything-claude-code

AI辅助开发的回归测试策略。沙盒模式API测试,无需依赖数据库,自动化的缺陷检查工作流程,以及捕捉AI盲点的模式,其中同一模型编写和审查代码。

Software TestingClaudeCursorCodex

rust-testing

144923
from affaan-m/everything-claude-code

Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. Follows TDD methodology.

DevelopmentClaude

kotlin-testing

144923
from affaan-m/everything-claude-code

Kotest, MockK, coroutine testi, property-based testing ve Kover coverage ile Kotlin test kalıpları. İdiomatic Kotlin uygulamalarıyla TDD metodolojisini takip eder.

DevelopmentClaude

cpp-testing

144923
from affaan-m/everything-claude-code

C++ テストの作成/更新/修正、GoogleTest/CTest の設定、失敗またはフレーキーなテストの診断、カバレッジ/サニタイザーの追加時にのみ使用します。

DevelopmentClaude

python-testing

144923
from affaan-m/everything-claude-code

Python testing best practices using pytest including fixtures, parametrization, mocking, coverage analysis, async testing, and test organization. Use when writing or improving Python tests.

DevelopmentClaude

golang-testing

144923
from affaan-m/everything-claude-code

Go testing best practices including table-driven tests, test helpers, benchmarking, race detection, coverage analysis, and integration testing patterns. Use when writing or improving Go tests.

DevelopmentClaude

e2e-testing

144923
from affaan-m/everything-claude-code

Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies.

Software TestingClaude