Project: a real pytest suite for AssetRegistry#
Read notes/01-pytest-fundamentals.md through notes/04-ape-and-pytest.md before starting this; the exercises in exercises/ are optional, before or after. Everything below assumes you already understand test discovery, fixtures, parametrize, and specifically how Ape's accounts/project/chain fixtures and its per-test snapshot-and-revert isolation work. This project is where you use that understanding, not where you first encounter it.
What you're building#
A tests/test_asset_registry.py file, added to your existing Ape project from Weeks 5 through 7 (the same project, whichever track you built it in), giving AssetRegistry a real pytest suite through Ape's pytest plugin.
This module has no contracts/ or scripts/ folder of its own, the same as Weeks 6 and 7: there's no new project to build and nothing new to deploy to a testnet. You're adding tests to the exact AssetRegistry you already wrote back in Week 5.
Where this goes#
tests/ is Ape's own conventional folder for test files, a sibling to contracts/ and scripts/, at your project's root. If you don't already have one, create it. ape test looks for tests there by default, the same way ape compile looks for source in contracts/.
Your tests don't touch your real testnet deployment#
This is worth being explicit about before you write anything. As notes/04-ape-and-pytest.md covers, ape test runs against a fast, local, disposable chain, not Sepolia or whichever testnet you deployed to in Week 5. Every test in test_asset_registry.py deploys its own fresh copy of AssetRegistry onto that local chain (the same pattern the Counter example in that note used), and Ape throws the local chain's state away and starts clean before the next test runs. The real contract address you've been passing to interact.py, and everything Weeks 6 and 7 built against it, is completely untouched by any of this.
Function names differ by track, mechanics don't#
If you built the Solidity track, your functions are registerHolder, updateBalance, setEligibility, getHolder (camelCase); the source is weeks/05-guided-build/blockchain/project/contracts/AssetRegistry.sol. If you built the Vyper track, they're register_holder, update_balance, set_eligibility, get_holder (snake_case); the source is weeks/05-guided-build/blockchain-vyper/project/contracts/AssetRegistry.vy. Everything else, the fixture pattern, parametrize, ape test, the accounts/project/chain fixtures, and the revert-testing pattern below, is identical regardless of which track you're in. Conveniently, both tracks' assert/require messages are worded identically ("AssetRegistry: caller is not the owner", "AssetRegistry: holder already registered", "AssetRegistry: holder not registered"), so the revert tests below need no track-specific wording at all.
Testing a revert: ape.reverts#
Two of the four required cases below need to prove a transaction reverts, not that it succeeds. pytest's own general tool for asserting an exception, pytest.raises(...) (see notes/03-parametrize.md), would technically work here too, but Ape gives you a purpose-built equivalent that also lets you check the revert's actual message:
import ape
def test_registering_same_holder_twice_reverts(registry, accounts):
owner, holder = accounts[0], accounts[1]
registry.register_holder(holder, True, 100, sender=owner) # registerHolder(...) on the Solidity track
with ape.reverts("AssetRegistry: holder already registered"):
registry.register_holder(holder, True, 100, sender=owner)
What to test#
At minimum, four cases:
- Registering a new holder succeeds. Call
register_holder/registerHolderonce and confirm the state it wrote (get_holder/getHolder, or the freeholders(address)getter, spelled the same way in both tracks, either works) matches what you passed in. - Registering the same holder twice reverts. With the message
"AssetRegistry: holder already registered". - A non-owner calling a protected function reverts. Pick any of the three owner-only functions; call it with
sender=set to an account other than whoever deployed the registry, and confirm it reverts with"AssetRegistry: caller is not the owner". - A balance or eligibility update actually changes the value it's supposed to. Register a holder, then call
update_balance/updateBalanceorset_eligibility/setEligibility, and confirm the change is reflected when you read the holder back.
A reasonable starting shape, a fixture other tests can reuse, plus the first test fully written:
import pytest
@pytest.fixture
def registry(project, accounts):
owner = accounts[0]
return owner.deploy(project.AssetRegistry) # a fresh instance, on the local chain, for every test
def test_register_holder_succeeds(registry, accounts):
owner, holder = accounts[0], accounts[1]
registry.register_holder(holder, True, 100, sender=owner) # registerHolder(...) on the Solidity track
balance, eligible, registered, _ = registry.get_holder(holder) # getHolder(...) on the Solidity track
assert balance == 100
assert eligible is True
assert registered is True
Write the other three yourself, following that same shape: the registry fixture for a freshly deployed contract, accounts for whoever's calling, and either a plain assert against the state you read back, or an ape.reverts(...) block around a call you expect to fail.
Running it#
From your project's root:
ape test
Add -v for the same per-test verbosity pytest -v gives you. You should see at least four passing tests.
Optional stretch: bring parametrize into the real project#
notes/03-parametrize.md covers running one test against several inputs. A natural place to apply that here: parametrize a test that calls update_balance/updateBalance with several different new balances (0, 1, a large number) and asserts each one lands correctly, or one that calls set_eligibility/setEligibility with both True and False and confirms the flag actually flips both ways. Neither is required; both are a genuine rep of connecting parametrize to a real, stateful contract instead of only the toy shipping_cost function from the exercises.
What "done" looks like#
Run ape test and watch your four (or more) tests pass. Then, the same discipline weeks/07-low-scaffolding-continued/blockchain/notes/02-why-tests-matter-here.md asked for back in Week 7, deliberately break something your test suite is supposed to catch: comment out the assert not self.holders[holder].registered, ... line (Vyper) or the equivalent require(!holders[holder].registered, ...) line (Solidity) in your contract, recompile, and rerun ape test. The "registering the same holder twice reverts" test should fail, clearly, for the reason you expect. If it doesn't, the test wasn't actually testing what you thought it was, and that's worth fixing before you consider this module finished. Put the line back afterward.
Status#
There is deliberately no solutions/ folder for this project step, the same reasoning as every other project in this curriculum: writing real tests against your own real project, in whichever track you built it, is the point, not matching a reference answer.