dojo-test
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDojo Test Generation
Dojo 测试生成
Write comprehensive tests for your Dojo models and systems using Cairo's test framework and Dojo's test utilities.
使用Cairo的测试框架和Dojo的测试工具为你的Dojo模型与系统编写全面测试。
When to Use This Skill
适用场景
- "Write tests for the move system"
- "Test the Position model"
- "Add unit tests for combat logic"
- "Create integration tests"
- "为移动系统编写测试"
- "测试Position模型"
- "为战斗逻辑添加单元测试"
- "创建集成测试"
What This Skill Does
功能说明
Generates test files with:
- setup
spawn_test_world() - Model and system registration
- Test functions with assertions
- Cheat code usage for manipulating execution context
- State verification
生成包含以下内容的测试文件:
- 初始化设置
spawn_test_world() - 模型与系统注册
- 带断言的测试函数
- 用于操纵执行上下文的作弊码使用
- 状态验证
Quick Start
快速开始
Interactive mode:
"Write tests for the spawn system"I'll ask about:
- What to test (models, systems, or both)
- Test scenarios (happy path, edge cases)
- State assertions needed
Direct mode:
"Test that the move system correctly updates Position"交互式模式:
"Write tests for the spawn system"我会询问以下信息:
- 测试对象(模型、系统或两者)
- 测试场景(正常流程、边缘情况)
- 需要的状态断言
直接模式:
"Test that the move system correctly updates Position"Running Tests
运行测试
bash
undefinedbash
undefinedRun all tests
Run all tests
sozo test
sozo test
Run specific test
Run specific test
sozo test test_spawn
undefinedsozo test test_spawn
undefinedTest Structure
测试结构
Unit Tests (in model files)
单元测试(在模型文件中)
Place unit tests in the same file as the model:
cairo
// models.cairo
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Position {
#[key]
player: ContractAddress,
vec: Vec2,
}
#[cfg(test)]
mod tests {
use super::{Position, Vec2, Vec2Trait};
#[test]
fn test_vec_is_zero() {
assert!(Vec2Trait::is_zero(Vec2 { x: 0, y: 0 }), "not zero");
}
#[test]
fn test_vec_is_equal() {
let position = Vec2 { x: 420, y: 0 };
assert!(position.is_equal(Vec2 { x: 420, y: 0 }), "not equal");
}
}将单元测试放在与模型相同的文件中:
cairo
// models.cairo
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Position {
#[key]
player: ContractAddress,
vec: Vec2,
}
#[cfg(test)]
mod tests {
use super::{Position, Vec2, Vec2Trait};
#[test]
fn test_vec_is_zero() {
assert!(Vec2Trait::is_zero(Vec2 { x: 0, y: 0 }), "not zero");
}
#[test]
fn test_vec_is_equal() {
let position = Vec2 { x: 420, y: 0 };
assert!(position.is_equal(Vec2 { x: 420, y: 0 }), "not equal");
}
}Integration Tests
集成测试
Create a directory for system integration tests:
testscairo
// tests/test_move.cairo
#[cfg(test)]
mod tests {
use dojo::model::{ModelStorage, ModelValueStorage, ModelStorageTest};
use dojo::world::WorldStorageTrait;
use dojo_cairo_test::{spawn_test_world, NamespaceDef, TestResource, ContractDefTrait};
use dojo_starter::systems::actions::{actions, IActionsDispatcher, IActionsDispatcherTrait};
use dojo_starter::models::{Position, m_Position, Moves, m_Moves, Direction};
fn namespace_def() -> NamespaceDef {
NamespaceDef {
namespace: "dojo_starter",
resources: [
TestResource::Model(m_Position::TEST_CLASS_HASH),
TestResource::Model(m_Moves::TEST_CLASS_HASH),
TestResource::Event(actions::e_Moved::TEST_CLASS_HASH),
TestResource::Contract(actions::TEST_CLASS_HASH)
].span()
}
}
fn contract_defs() -> Span<ContractDef> {
[
ContractDefTrait::new(@"dojo_starter", @"actions")
.with_writer_of([dojo::utils::bytearray_hash(@"dojo_starter")].span())
].span()
}
#[test]
fn test_move() {
let caller = starknet::contract_address_const::<0x0>();
let ndef = namespace_def();
let mut world = spawn_test_world([ndef].span());
// Sync permissions and initializations
world.sync_perms_and_inits(contract_defs());
// Get contract address from DNS
let (contract_address, _) = world.dns(@"actions").unwrap();
let actions_system = IActionsDispatcher { contract_address };
// Spawn player
actions_system.spawn();
// Read initial state
let initial_moves: Moves = world.read_model(caller);
let initial_position: Position = world.read_model(caller);
assert(
initial_position.vec.x == 10 && initial_position.vec.y == 10,
"wrong initial position"
);
// Move right
actions_system.move(Direction::Right(()));
// Verify state changes
let moves: Moves = world.read_model(caller);
assert(moves.remaining == initial_moves.remaining - 1, "moves is wrong");
let new_position: Position = world.read_model(caller);
assert(new_position.vec.x == initial_position.vec.x + 1, "position x is wrong");
assert(new_position.vec.y == initial_position.vec.y, "position y is wrong");
}
}创建目录用于系统集成测试:
testscairo
// tests/test_move.cairo
#[cfg(test)]
mod tests {
use dojo::model::{ModelStorage, ModelValueStorage, ModelStorageTest};
use dojo::world::WorldStorageTrait;
use dojo_cairo_test::{spawn_test_world, NamespaceDef, TestResource, ContractDefTrait};
use dojo_starter::systems::actions::{actions, IActionsDispatcher, IActionsDispatcherTrait};
use dojo_starter::models::{Position, m_Position, Moves, m_Moves, Direction};
fn namespace_def() -> NamespaceDef {
NamespaceDef {
namespace: "dojo_starter",
resources: [
TestResource::Model(m_Position::TEST_CLASS_HASH),
TestResource::Model(m_Moves::TEST_CLASS_HASH),
TestResource::Event(actions::e_Moved::TEST_CLASS_HASH),
TestResource::Contract(actions::TEST_CLASS_HASH)
].span()
}
}
fn contract_defs() -> Span<ContractDef> {
[
ContractDefTrait::new(@"dojo_starter", @"actions")
.with_writer_of([dojo::utils::bytearray_hash(@"dojo_starter")].span())
].span()
}
#[test]
fn test_move() {
let caller = starknet::contract_address_const::<0x0>();
let ndef = namespace_def();
let mut world = spawn_test_world([ndef].span());
// Sync permissions and initializations
world.sync_perms_and_inits(contract_defs());
// Get contract address from DNS
let (contract_address, _) = world.dns(@"actions").unwrap();
let actions_system = IActionsDispatcher { contract_address };
// Spawn player
actions_system.spawn();
// Read initial state
let initial_moves: Moves = world.read_model(caller);
let initial_position: Position = world.read_model(caller);
assert(
initial_position.vec.x == 10 && initial_position.vec.y == 10,
"wrong initial position"
);
// Move right
actions_system.move(Direction::Right(()));
// Verify state changes
let moves: Moves = world.read_model(caller);
assert(moves.remaining == initial_moves.remaining - 1, "moves is wrong");
let new_position: Position = world.read_model(caller);
assert(new_position.vec.x == initial_position.vec.x + 1, "position x is wrong");
assert(new_position.vec.y == initial_position.vec.y, "position y is wrong");
}
}Testing Model Read/Write
测试模型的读/写操作
cairo
#[test]
fn test_world_test_set() {
let caller = starknet::contract_address_const::<0x0>();
let ndef = namespace_def();
let mut world = spawn_test_world([ndef].span());
// Test initial position (default zero)
let mut position: Position = world.read_model(caller);
assert(position.vec.x == 0 && position.vec.y == 0, "initial position wrong");
// Test write_model_test (bypasses permissions)
position.vec.x = 122;
position.vec.y = 88;
world.write_model_test(@position);
let mut position: Position = world.read_model(caller);
assert(position.vec.y == 88, "write_model_test failed");
// Test model deletion
world.erase_model(@position);
let position: Position = world.read_model(caller);
assert(position.vec.x == 0 && position.vec.y == 0, "erase_model failed");
}cairo
#[test]
fn test_world_test_set() {
let caller = starknet::contract_address_const::<0x0>();
let ndef = namespace_def();
let mut world = spawn_test_world([ndef].span());
// Test initial position (default zero)
let mut position: Position = world.read_model(caller);
assert(position.vec.x == 0 && position.vec.y == 0, "initial position wrong");
// Test write_model_test (bypasses permissions)
position.vec.x = 122;
position.vec.y = 88;
world.write_model_test(@position);
let mut position: Position = world.read_model(caller);
assert(position.vec.y == 88, "write_model_test failed");
// Test model deletion
world.erase_model(@position);
let position: Position = world.read_model(caller);
assert(position.vec.x == 0 && position.vec.y == 0, "erase_model failed");
}Cheat Codes
作弊码
Use starknet's built-in testing cheat codes to manipulate execution context:
使用Starknet内置的测试作弊码来操纵执行上下文:
Set Caller Address
设置调用者地址
cairo
use starknet::{testing, contract_address_const};
#[test]
fn test_as_different_caller() {
let player1 = contract_address_const::<'player1'>();
testing::set_caller_address(player1);
// Now get_caller_address() returns player1
}cairo
use starknet::{testing, contract_address_const};
#[test]
fn test_as_different_caller() {
let player1 = contract_address_const::<'player1'>();
testing::set_caller_address(player1);
// Now get_caller_address() returns player1
}Set Contract Address
设置合约地址
cairo
use starknet::{testing, contract_address_const};
#[test]
fn test_with_contract_address() {
let contract = contract_address_const::<'contract'>();
testing::set_contract_address(contract);
// Now get_contract_address() returns contract
}cairo
use starknet::{testing, contract_address_const};
#[test]
fn test_with_contract_address() {
let contract = contract_address_const::<'contract'>();
testing::set_contract_address(contract);
// Now get_contract_address() returns contract
}Set Block Timestamp
设置区块时间戳
cairo
use starknet::testing;
#[test]
fn test_with_timestamp() {
testing::set_block_timestamp(123456);
// Now get_block_timestamp() returns 123456
}cairo
use starknet::testing;
#[test]
fn test_with_timestamp() {
testing::set_block_timestamp(123456);
// Now get_block_timestamp() returns 123456
}Set Block Number
设置区块编号
cairo
use starknet::testing;
#[test]
fn test_with_block_number() {
testing::set_block_number(1234567);
// Now get_block_number() returns 1234567
}cairo
use starknet::testing;
#[test]
fn test_with_block_number() {
testing::set_block_number(1234567);
// Now get_block_number() returns 1234567
}Test Patterns
测试模式
Test Expected Panic
测试预期恐慌
cairo
#[test]
#[should_panic(expected: ('No moves remaining',))]
fn test_no_moves_remaining() {
// Setup with zero moves
// ...
actions_system.move(Direction::Right(())); // Should panic
}cairo
#[test]
#[should_panic(expected: ('No moves remaining',))]
fn test_no_moves_remaining() {
// Setup with zero moves
// ...
actions_system.move(Direction::Right(())); // Should panic
}Test Multiple Players
测试多玩家场景
cairo
#[test]
fn test_two_players() {
let player1 = contract_address_const::<0x111>();
let player2 = contract_address_const::<0x222>();
// Player 1 actions
testing::set_contract_address(player1);
actions_system.spawn();
// Player 2 actions
testing::set_contract_address(player2);
actions_system.spawn();
// Verify both have independent state
let pos1: Position = world.read_model(player1);
let pos2: Position = world.read_model(player2);
}cairo
#[test]
fn test_two_players() {
let player1 = contract_address_const::<0x111>();
let player2 = contract_address_const::<0x222>();
// Player 1 actions
testing::set_contract_address(player1);
actions_system.spawn();
// Player 2 actions
testing::set_contract_address(player2);
actions_system.spawn();
// Verify both have independent state
let pos1: Position = world.read_model(player1);
let pos2: Position = world.read_model(player2);
}Test State Transitions
测试状态流转
cairo
#[test]
fn test_spawn_then_move() {
// Initial state
actions_system.spawn();
let initial: Position = world.read_model(caller);
// Transition
actions_system.move(Direction::Right(()));
// Verify
let after: Position = world.read_model(caller);
assert(after.vec.x == initial.vec.x + 1, "did not move right");
}cairo
#[test]
fn test_spawn_then_move() {
// Initial state
actions_system.spawn();
let initial: Position = world.read_model(caller);
// Transition
actions_system.move(Direction::Right(()));
// Verify
let after: Position = world.read_model(caller);
assert(after.vec.x == initial.vec.x + 1, "did not move right");
}Key Test Utilities
核心测试工具
| Function | Purpose |
|---|---|
| Create test world with models |
| Sync permissions |
| Get contract address by name |
| Read model state |
| Write model (bypass permissions) |
| Delete model |
| 函数 | 用途 |
|---|---|
| 创建包含模型的测试环境 |
| 同步权限 |
| 通过名称获取合约地址 |
| 读取模型状态 |
| 写入模型(绕过权限) |
| 删除模型 |
Test Organization
测试组织
src/
├── models.cairo # Include unit tests in #[cfg(test)] mod
├── systems/
│ └── actions.cairo # Include unit tests in #[cfg(test)] mod
└── tests/
└── test_world.cairo # Integration testssrc/
├── models.cairo # Include unit tests in #[cfg(test)] mod
├── systems/
│ └── actions.cairo # Include unit tests in #[cfg(test)] mod
└── tests/
└── test_world.cairo # Integration testsNext Steps
后续步骤
After writing tests:
- Run to execute
sozo test - Use skill to verify test coverage
dojo-review - Run tests before deploying with
dojo-deploy
编写测试后:
- 运行执行测试
sozo test - 使用技能验证测试覆盖率
dojo-review - 在部署前运行测试,配合技能
dojo-deploy
Related Skills
相关技能
- dojo-model: Create models to test
- dojo-system: Create systems to test
- dojo-review: Review test coverage
- dojo-deploy: Deploy after tests pass
- dojo-model: 创建待测试的模型
- dojo-system: 创建待测试的系统
- dojo-review: 检查测试覆盖率
- dojo-deploy: 测试通过后部署