When to use
Load this when a Bevy crate needs unit or integration tests. cargo test runs #[test] functions. A headless App with MinimalPlugins is the harness for schedule logic.
ECS shape
- Command:
cargo testcompiles and runs unit tests, integration tests, and documentation tests.cargo test name_filterruns names that contain the filter. Arguments after--go to libtest (cargo test -- --help). - What runs: functions annotated with
#[test]. The harness runs them in multiple threads. The working directory for each test is the package root, so relative paths resolve from that root no matter where you invoked Cargo. - Scope: default
cargo testcovers the lib, bins as unit tests, integration tests, and doc tests, and it builds examples.--liblimits to the library.harness = falseon a target means you supplymain. - Profile: the
testprofile is the default.--profile=debugkeeps debug info.-- --no-captureshows test output. - Bevy types inside a test: build an
App, insert the resource, add the system, callupdate, then readworld(). Keep the window out of the harness withMinimalPlugins.
How to run
| Need | Command |
|---|---|
| Whole suite | cargo test from the package |
| One name | cargo test name_filter |
| Printed output | cargo test -- --no-capture |
Pure logic stays a normal #[test] with no App.
fn score_after_pickup(score: u32, value: u32) -> u32 {
score.saturating_add(value)
}
#[test]
fn pickup_adds_points() {
assert_eq!(score_after_pickup(1, 2), 3);
}
Schedule tests own a headless app. world() is shared for the assert, so the query that writes Score has already finished.
#[derive(Resource, Default)]
struct Score(u32);
fn add_point(mut score: ResMut<Score>) {
score.0 += 1;
}
#[test]
fn one_update_adds_a_point() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.insert_resource(Score(0))
.add_systems(Update, add_point);
app.update();
assert_eq!(app.world().resource::<Score>().0, 1);
}
Spawn in Startup, then step Update once. The test reads the component through a one-off system result stored on a resource the test inserts, or through world().
#[derive(Component)]
struct Marker;
fn spawn_marker(mut commands: Commands) {
commands.spawn(Marker);
}
#[test]
fn startup_spawns_one_marker() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.add_systems(Startup, spawn_marker);
app.update();
let count = app.world().query::<&Marker>().iter(app.world()).count();
assert_eq!(count, 1);
}
Two writers of the same component are chained inside the test the same way they are chained in the game plugin.
#[derive(Resource)]
struct Wallet(i32);
fn earn(mut wallet: ResMut<Wallet>) {
wallet.0 += 5;
}
fn spend(mut wallet: ResMut<Wallet>) {
wallet.0 -= 2;
}
#[test]
fn earn_then_spend() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.insert_resource(Wallet(0))
.add_systems(Update, (earn, spend).chain());
app.update();
assert_eq!(app.world().resource::<Wallet>().0, 3);
}
Scope
- The harness above is
MinimalPlugins,app.update(), andworld(). It does not capture frames or inject window input. - Doctest working-directory rules differ from unit tests. Do not assume a doctest can read
assets/the same way a#[test]can. The package root is the unit-test working directory.
Expert Knowledge Delta
Target: Bevy 0.20+.
Core ECS types: Component, Resource, Query, Commands, Startup, Update, Fixed / FixedMain (bevy::time), .chain(), Plugin, DefaultPlugins, MinimalPlugins. Messages and observers are an ownership slot. See bevy-events-messaging for their APIs. Systems run in parallel unless .chain() orders the ones that conflict.
Who owns state
| Piece | Owner | Who may mutate | Schedule |
|---|---|---|---|
| Data on one entity | Component |
One system via Query |
Update or FixedMain |
| One value for the whole app | Resource |
One system | Startup to insert, then the schedule that reads it |
| Spawn | Commands |
The spawning system | Startup for the initial world; later schedules for runtime spawn |
| Order that must not race | .chain() |
The chained systems only | Same schedule |
| Plugin boundary | Plugin |
build registers systems and resources |
Before the app runs |
Borrow rule: two systems that both mutably query the same component must be ordered (.chain()) or split so only one writes. Unrelated systems stay parallel. That is the ECS page rule, not a guess at a system-set API.
Decision
| If you need | Do this | Do not |
|---|---|---|
| Per-entity data | Component + Query |
A free-floating struct outside the World |
| App-wide config or clock | Resource |
Copy the value onto every entity |
| Stable simulation step | FixedMain and the Fixed clock |
Tie simulation to raw frame Update |
| Startup spawn | Commands in Startup |
Spawn from a render system |
| Cross-system notice that is not shared state | Message or observer slot in bevy-events-messaging |
A new event type invented in this file |
| An API outside the documented set | Check the official docs first | A made-up crate or API |
Load references/ecs-ownership.md for the ownership table.
NEVER
-
Do not pin
/0.20.0/in docs URLs. WHY: links stay onlatest; the 0.20 target is stated once in prose. -
Do not mutably query the same component from two unordered systems. WHY: the schedule runs systems in parallel unless
.chain()orders them. -
Do not invent a physics, navigation, networking, or XR crate here. WHY: this skill only names documented Bevy crates and modules.
-
No scene-tree test runner in this crate. WHY: schedule tests use
MinimalPlugins,app.update(), andworld(). -
No frame-capture or input-injection harness. WHY: this skill tests schedules with
MinimalPluginsandapp.update(). -
No third-party Bevy test crates. WHY: Bevy code stays idiomatic ECS and uses only documented Bevy APIs.
Reference
The library pin on bevy-master stays 0.20.
Official Documentation
- cargo test -
#[test], filters, libtest args, package root as cwd. - Examples - Example tree on the
latestbranch. Examples are not the test harness.
Related Skills
Prerequisites
- bevy-master - Who owns which piece of state.
- bevy-project-foundations - The Cargo package
cargo testruns against.
Downstream / consumers
- None yet.
Master
- bevy-master - Bevy router.