Skip to content
This repository was archived by the owner on Aug 1, 2025. It is now read-only.

Commit 445924d

Browse files
authored
feat: rework testing, add Snforge (#289)
* feat: rework testing, add Snforge * fix typos
1 parent 8499f23 commit 445924d

File tree

10 files changed

+351
-274
lines changed

10 files changed

+351
-274
lines changed

Scarb.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ version = "0.1.0"
335335
[[package]]
336336
name = "testing_how_to"
337337
version = "0.1.0"
338+
dependencies = [
339+
"snforge_std",
340+
]
338341

339342
[[package]]
340343
name = "timelock"

listings/getting-started/testing_how_to/Scarb.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ edition.workspace = true
77
starknet.workspace = true
88

99
[dev-dependencies]
10-
cairo_test.workspace = true
10+
assert_macros.workspace = true
11+
snforge_std.workspace = true
1112

1213
[scripts]
1314
test.workspace = true

listings/getting-started/testing_how_to/src/contract.cairo

Lines changed: 58 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,73 @@
11
// [!region contract]
22
#[starknet::interface]
3-
pub trait ISimpleContract<TContractState> {
4-
fn get_value(self: @TContractState) -> u32;
5-
fn get_owner(self: @TContractState) -> starknet::ContractAddress;
6-
fn set_value(ref self: TContractState, value: u32);
3+
pub trait IInventoryContract<TContractState> {
4+
fn get_inventory_count(self: @TContractState) -> u32;
5+
fn get_max_capacity(self: @TContractState) -> u32;
6+
fn update_inventory(ref self: TContractState, new_count: u32);
7+
}
8+
9+
/// An external function that encodes constraints for update inventory
10+
fn check_update_inventory(new_count: u32, max_capacity: u32) -> Result<u32, felt252> {
11+
if new_count == 0 {
12+
return Result::Err('OutOfStock');
13+
}
14+
if new_count > max_capacity {
15+
return Result::Err('ExceedsCapacity');
16+
}
17+
18+
Result::Ok(new_count)
719
}
820

921
#[starknet::contract]
10-
pub mod SimpleContract {
22+
pub mod InventoryContract {
23+
use super::check_update_inventory;
1124
use starknet::{get_caller_address, ContractAddress};
1225
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
1326

1427
#[storage]
15-
struct Storage {
16-
pub value: u32,
28+
pub struct Storage {
29+
pub inventory_count: u32,
30+
pub max_capacity: u32,
1731
pub owner: ContractAddress,
1832
}
1933

34+
#[event]
35+
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
36+
pub enum Event {
37+
InventoryUpdated: InventoryUpdated,
38+
}
39+
40+
#[derive(Copy, Drop, Debug, PartialEq, starknet::Event)]
41+
pub struct InventoryUpdated {
42+
pub new_count: u32,
43+
}
44+
2045
#[constructor]
21-
pub fn constructor(ref self: ContractState, initial_value: u32) {
22-
self.value.write(initial_value);
46+
pub fn constructor(ref self: ContractState, max_capacity: u32) {
47+
self.inventory_count.write(0);
48+
self.max_capacity.write(max_capacity);
2349
self.owner.write(get_caller_address());
2450
}
2551

2652
#[abi(embed_v0)]
27-
pub impl SimpleContractImpl of super::ISimpleContract<ContractState> {
28-
fn get_value(self: @ContractState) -> u32 {
29-
self.value.read()
53+
pub impl InventoryContractImpl of super::IInventoryContract<ContractState> {
54+
fn get_inventory_count(self: @ContractState) -> u32 {
55+
self.inventory_count.read()
3056
}
3157

32-
fn get_owner(self: @ContractState) -> ContractAddress {
33-
self.owner.read()
58+
fn get_max_capacity(self: @ContractState) -> u32 {
59+
self.max_capacity.read()
3460
}
3561

36-
fn set_value(ref self: ContractState, value: u32) {
62+
fn update_inventory(ref self: ContractState, new_count: u32) {
3763
assert(self.owner.read() == get_caller_address(), 'Not owner');
38-
self.value.write(value);
64+
65+
match check_update_inventory(new_count, self.max_capacity.read()) {
66+
Result::Ok(new_count) => self.inventory_count.write(new_count),
67+
Result::Err(error) => { panic!("{}", error); },
68+
}
69+
70+
self.emit(Event::InventoryUpdated(InventoryUpdated { new_count }));
3971
}
4072
}
4173
}
@@ -44,178 +76,24 @@ pub mod SimpleContract {
4476
// [!region tests]
4577
#[cfg(test)]
4678
mod tests {
47-
// Import the interface and dispatcher to be able to interact with the contract.
48-
use super::{SimpleContract, ISimpleContractDispatcher, ISimpleContractDispatcherTrait};
49-
50-
// Import the deploy syscall to be able to deploy the contract.
51-
use starknet::syscalls::deploy_syscall;
52-
use starknet::{get_contract_address, contract_address_const};
53-
54-
// Use starknet test utils to fake the contract_address
55-
use starknet::testing::set_contract_address;
56-
57-
// Deploy the contract and return its dispatcher.
58-
fn deploy(initial_value: u32) -> ISimpleContractDispatcher {
59-
// Declare and deploy
60-
let (contract_address, _) = deploy_syscall(
61-
SimpleContract::TEST_CLASS_HASH.try_into().unwrap(),
62-
0,
63-
array![initial_value.into()].span(),
64-
false,
65-
)
66-
.unwrap();
67-
68-
// Return the dispatcher.
69-
// The dispatcher allows to interact with the contract based on its interface.
70-
ISimpleContractDispatcher { contract_address }
71-
}
79+
use super::check_update_inventory;
7280

7381
#[test]
74-
fn test_deploy() {
75-
let initial_value: u32 = 10;
76-
let contract = deploy(initial_value);
77-
78-
assert_eq!(contract.get_value(), initial_value);
79-
assert_eq!(contract.get_owner(), get_contract_address());
82+
fn test_check_update_inventory() {
83+
let result = check_update_inventory(10, 100);
84+
assert_eq!(result, Result::Ok(10));
8085
}
8186

8287
#[test]
83-
fn test_set_as_owner() {
84-
// Fake the contract address to owner
85-
let owner = contract_address_const::<'owner'>();
86-
set_contract_address(owner);
87-
88-
// When deploying the contract, the owner is the caller.
89-
let contract = deploy(10);
90-
assert_eq!(contract.get_owner(), owner);
91-
92-
// As the current caller is the owner, the value can be set.
93-
let new_value: u32 = 20;
94-
contract.set_value(new_value);
95-
96-
assert_eq!(contract.get_value(), new_value);
88+
fn test_check_update_inventory_out_of_stock() {
89+
let result = check_update_inventory(0, 100);
90+
assert_eq!(result, Result::Err('OutOfStock'));
9791
}
9892

9993
#[test]
100-
#[should_panic]
101-
fn test_set_not_owner() {
102-
let owner = contract_address_const::<'owner'>();
103-
set_contract_address(owner);
104-
let contract = deploy(10);
105-
106-
// Fake the contract address to another address
107-
let not_owner = contract_address_const::<'not owner'>();
108-
set_contract_address(not_owner);
109-
110-
// As the current caller is not the owner, the value cannot be set.
111-
let new_value: u32 = 20;
112-
contract.set_value(new_value);
113-
// Panic expected
114-
}
115-
116-
#[test]
117-
#[available_gas(150000)]
118-
fn test_deploy_gas() {
119-
deploy(10);
120-
}
121-
}
122-
// [!endregion tests]
123-
124-
// [!region tests_with_state]
125-
#[cfg(test)]
126-
mod tests_with_states {
127-
// Only import the contract and implementation
128-
use super::SimpleContract;
129-
use SimpleContract::SimpleContractImpl;
130-
131-
use starknet::contract_address_const;
132-
use starknet::testing::set_caller_address;
133-
use core::num::traits::Zero;
134-
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
135-
136-
#[test]
137-
fn test_standalone_state() {
138-
let mut state = SimpleContract::contract_state_for_testing();
139-
140-
// As no contract was deployed, the constructor was not called on the state
141-
// - with valueContractMemberStateTrait
142-
assert_eq!(state.value.read(), 0);
143-
// - with SimpleContractImpl
144-
assert_eq!(state.get_value(), 0);
145-
assert_eq!(state.owner.read(), Zero::zero());
146-
147-
// We can still directly call the constructor to initialize the state.
148-
let owner = contract_address_const::<'owner'>();
149-
// We are not setting the contract address but the caller address here,
150-
// as we are not deploying the contract but directly calling the constructor function.
151-
set_caller_address(owner);
152-
153-
let initial_value: u32 = 10;
154-
SimpleContract::constructor(ref state, initial_value);
155-
assert_eq!(state.get_value(), initial_value);
156-
assert_eq!(state.get_owner(), owner);
157-
158-
// As the current caller is the owner, the value can be set.
159-
let new_value: u32 = 20;
160-
state.set_value(new_value);
161-
assert_eq!(state.get_value(), new_value);
162-
}
163-
164-
// But we can also deploy the contract and interact with it using the dispatcher
165-
// as shown in the previous tests, and still use the state for testing.
166-
use super::{ISimpleContractDispatcher, ISimpleContractDispatcherTrait};
167-
use starknet::{syscalls::deploy_syscall, testing::set_contract_address};
168-
169-
#[test]
170-
fn test_state_with_contract() {
171-
let owner = contract_address_const::<'owner'>();
172-
let not_owner = contract_address_const::<'not owner'>();
173-
174-
// Deploy as owner
175-
let initial_value: u32 = 10;
176-
set_contract_address(owner);
177-
let (contract_address, _) = deploy_syscall(
178-
SimpleContract::TEST_CLASS_HASH.try_into().unwrap(),
179-
0,
180-
array![initial_value.into()].span(),
181-
false,
182-
)
183-
.unwrap();
184-
let mut contract = ISimpleContractDispatcher { contract_address };
185-
186-
// create the state
187-
// - Set back as not owner
188-
set_contract_address(not_owner);
189-
let mut state = SimpleContract::contract_state_for_testing();
190-
// - Currently, the state is not 'linked' to the contract
191-
assert_ne!(state.get_value(), initial_value);
192-
assert_ne!(state.get_owner(), owner);
193-
// - Link the state to the contract by setting the contract address
194-
set_contract_address(contract.contract_address);
195-
assert_eq!(state.get_value(), initial_value);
196-
assert_eq!(state.get_owner(), owner);
197-
198-
// Mutating the state from the contract changes the testing state
199-
set_contract_address(owner);
200-
let new_value: u32 = 20;
201-
contract.set_value(new_value);
202-
set_contract_address(contract.contract_address);
203-
assert_eq!(state.get_value(), new_value);
204-
205-
// Mutating the state from the testing state changes the contract state
206-
set_caller_address(owner);
207-
state.set_value(initial_value);
208-
assert_eq!(contract.get_value(), initial_value);
209-
210-
// Directly mutating the state allows to change state
211-
// in ways that are not allowed by the contract, such as changing the owner.
212-
let new_owner = contract_address_const::<'new owner'>();
213-
state.owner.write(new_owner);
214-
assert_eq!(contract.get_owner(), new_owner);
215-
216-
set_caller_address(new_owner);
217-
state.set_value(new_value);
218-
assert_eq!(contract.get_value(), new_value);
94+
fn test_check_update_inventory_exceeds_capacity() {
95+
let result = check_update_inventory(101, 100);
96+
assert_eq!(result, Result::Err('ExceedsCapacity'));
21997
}
22098
}
22199
// [!endregion tests]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
mod contract;
2+
pub use contract::{
3+
InventoryContract, IInventoryContractDispatcher, IInventoryContractDispatcherTrait,
4+
};

0 commit comments

Comments
 (0)