| Crates.io | bevy-test-suite |
| lib.rs | bevy-test-suite |
| version | 0.1.0 |
| created_at | 2025-09-25 15:54:48.583538+00 |
| updated_at | 2025-09-25 15:54:48.583538+00 |
| description | The #[bevy_test] attribute you've been waiting for, plus powerful declarative testing for Bevy |
| homepage | |
| repository | https://github.com/noahsabaj/bevy-test-suite |
| max_upload_size | |
| id | 1854794 |
| size | 244,074 |
TWO ways to test Bevy apps: The #[bevy_test] attribute you asked for, plus declarative test_scenario! macros for complex integration tests. 80% less boilerplate, 100% less pain.
The Bevy community has been asking for #[bevy::test] to eliminate testing boilerplate. We deliver that AND more:
#[bevy_test] - The attribute macro the community requested for simple teststest_scenario! - Declarative testing for complex integration scenariosAdd to your Cargo.toml:
[dev-dependencies]
bevy-test-suite = "0.1"
bevy = "0.16"
#[bevy_test] AttributeThe familiar Rust testing experience with zero boilerplate:
use bevy::prelude::*;
use bevy_test_suite::{bevy_test, bevy_test_utils};
// Generate test utilities including TestApp trait
bevy_test_utils!();
#[bevy_test]
fn test_player_movement(app: &mut TestApp) {
let player = app.spawn(Player { position: Vec3::ZERO });
app.advance_time(1.0);
assert!(app.query::<&Position>().single().x > 0.0);
}
// With configuration options
#[bevy_test(headless, timeout = 1000)]
fn test_for_ci(app: &mut TestApp) {
// Runs without GPU/window requirements
}
For complex scenarios that read like specifications:
use bevy::prelude::*;
use bevy_test_suite::{test_scenario, bevy_test_utils};
// Generate test utilities
bevy_test_utils!();
test_scenario!(player_takes_damage {
given: {
resources: [Time::default()],
entities: [
Player {
type_name: Player,
fields: {
health: 100,
armor: 10
}
}
]
},
when: {
event: DamageEvent { target: entity_0, amount: 30 },
advance: 1.second()
},
then: {
entity_0.get::<Player>().unwrap().health == 73
}
});
Test individual systems in isolation:
test_system!(movement_system_test {
setup: {
resources: [Time::default()],
player: (Position(Vec3::ZERO), Velocity(Vec3::X * 10.0))
},
call: movement_system,
expect: {
app.world().entity(player).get::<Position>().unwrap().0.x > 0.0
}
});
Test component behavior and state transitions:
test_component!(health_component {
given: Health(100),
operations: [
take_damage(30) => Health(70),
heal(20) => Health(90),
take_damage(100) => Health(0)
]
});
Easily control time in your tests:
test_scenario!(test_over_time {
// ...
when: {
advance: 10.frames(), // Advance 10 frames
advance: 5.seconds(), // Advance 5 seconds
advance: 2.days() // Advance 2 game days
},
// ...
});
Automatically generate test cases to verify invariants:
property_test!(health_invariants {
given: {
max_health: 1..=1000,
damage_amounts: vec(0..=500, 0..10)
},
invariants: [
"Health never negative",
"Health never exceeds max",
"Healing beyond max is capped"
]
});
#[bevy_test]When using the #[bevy_test] attribute macro, you must first generate the TestApp trait:
// At the top of your test file
bevy_test_utils!(); // This generates TestApp and other utilities
#[bevy_test]
fn my_test(app: &mut TestApp) {
// Your test code
}
Create test worlds and inputs with builder patterns:
let world = MockWorld::new()
.with_entities(100)
.with_random_components::<Transform>()
.with_resource(GameSettings::default())
.build();
let input = MockInput::new()
.press(KeyCode::Space)
.wait(0.5)
.mouse_move(Vec2::new(100.0, 200.0))
.click(MouseButton::Left)
.apply_to(&mut app);
Use powerful assertion macros beyond simple equality:
assert_entity_count!(app, Player, 1);
assert_component_changed!(app, Transform);
assert_event_sent!(app, CollisionEvent);
assert_resource_exists!(app, GameSettings);
assert_query_empty!(app, Query<&Dead>);
assert_parent_child!(app, parent_entity, child_entity);
assert_approx_eq!(position.x, 100.0, 0.001);
#[test]
fn test_damage_manual() {
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.insert_resource(Time::default());
app.add_event::<DamageEvent>();
app.add_systems(Update, damage_system);
let player = app.world_mut().spawn(Player {
health: 100,
position: Vec3::ZERO,
}).id();
app.world_mut().send_event(DamageEvent {
target: player,
amount: 30,
});
app.update();
let player_health = app.world()
.entity(player)
.get::<Player>()
.unwrap()
.health;
assert_eq!(player_health, 70);
}
#[bevy_test] (10 lines - 66% reduction)#[bevy_test]
fn test_damage_attribute(app: &mut TestApp) {
let player = app.spawn(Player { health: 100, position: Vec3::ZERO });
app.send_event(DamageEvent { target: player, amount: 30 });
app.update();
assert_eq!(app.query::<&Player>().single().health, 70);
}
test_scenario! (5 lines - 83% reduction)test_scenario!(test_damage_declarative {
given: { entities: [Player { health: 100 }] },
when: { event: DamageEvent { amount: 30 } },
then: { Player[0].health == 70 }
});
Check out the examples/ directory for comprehensive examples:
dual_approach.rs - Shows both #[bevy_test] and declarative testingcomparison.rs - Side-by-side comparison of all three approachesbasic_scenario.rs - Introduction to scenario testingsystem_testing.rs - Testing individual systemsproperty_testing.rs - Property-based testing patternsRun examples with:
cargo test --example basic_scenario
bevy-test-suite adds zero runtime overhead. The macros expand at compile time to generate the same code you would write manually. Your tests run at the same speed, but you write 80% less code.
Use #[bevy_test] When |
Use test_scenario! When |
|---|---|
| Testing algorithms | Testing game scenarios |
| Need fine control | Want readable specs |
| Prefer imperative style | Prefer declarative style |
| Simple unit tests | Complex integration tests |
| Migrating from manual | Writing new test suites |
What the community asked for: #[bevy::test] attribute macro
What we deliver:
#[bevy_test] - The attribute you wantedtest_scenario! - Declarative testing for complex scenariosWhat we DON'T solve:
We give you TWO ways to test because different tests need different approaches:
#[bevy_test] - Familiar, imperative, great for unit teststest_scenario! - Declarative, readable, perfect for integration testsWe welcome contributions! Please see CONTRIBUTING.md for guidelines.
Licensed under either of:
at your option.
Special thanks to the Bevy community for feedback and suggestions.
Making Bevy testing as pleasant as using Bevy itself.