Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/tests/how_to_test_apps.rs
6584 views
1
//! Demonstrates simple integration testing of Bevy applications.
2
//!
3
//! By substituting [`DefaultPlugins`] with [`MinimalPlugins`], Bevy can run completely headless.
4
//!
5
//! The list of minimal plugins does not include things like window or input handling. The downside
6
//! of this is that resources or entities associated with those systems (for example:
7
//! `ButtonInput::<KeyCode>`) need to be manually added, either directly or via e.g.
8
//! [`InputPlugin`]. The upside, however, is that the test has complete control over these
9
//! resources, meaning we can fake user input, fake the window being moved around, and more.
10
use bevy::prelude::*;
11
12
#[derive(Component)]
13
struct Player {
14
mana: u32,
15
}
16
17
impl Default for Player {
18
fn default() -> Self {
19
Self { mana: 10 }
20
}
21
}
22
23
/// Splitting a Bevy project into multiple smaller plugins can make it more testable. We can
24
/// write tests for individual plugins in isolation, as well as for the entire project.
25
fn game_plugin(app: &mut App) {
26
app.add_systems(Startup, (spawn_player, window_title_system).chain());
27
app.add_systems(Update, spell_casting);
28
}
29
30
fn window_title_system(mut windows: Query<&mut Window>) {
31
for (index, mut window) in windows.iter_mut().enumerate() {
32
window.title = format!("This is window {index}!");
33
}
34
}
35
36
fn spawn_player(mut commands: Commands) {
37
commands.spawn(Player::default());
38
}
39
40
fn spell_casting(mut player: Query<&mut Player>, keyboard_input: Res<ButtonInput<KeyCode>>) {
41
if keyboard_input.just_pressed(KeyCode::Space) {
42
let Ok(mut player) = player.single_mut() else {
43
return;
44
};
45
46
if player.mana > 0 {
47
player.mana -= 1;
48
}
49
}
50
}
51
52
fn create_test_app() -> App {
53
let mut app = App::new();
54
55
// Note the use of `MinimalPlugins` instead of `DefaultPlugins`, as described above.
56
app.add_plugins(MinimalPlugins);
57
// Inserting a `KeyCode` input resource allows us to inject keyboard inputs, as if the user had
58
// pressed them.
59
app.insert_resource(ButtonInput::<KeyCode>::default());
60
61
// Spawning a fake window allows testing systems that require a window.
62
app.world_mut().spawn(Window::default());
63
64
app
65
}
66
67
#[test]
68
fn test_player_spawn() {
69
let mut app = create_test_app();
70
app.add_plugins(game_plugin);
71
72
// The `update` function needs to be called at least once for the startup
73
// systems to run.
74
app.update();
75
76
// Now that the startup systems have run, we can check if the player has
77
// spawned as expected.
78
let expected = Player::default();
79
let actual = app.world_mut().query::<&Player>().single(app.world());
80
assert!(actual.is_ok(), "There should be exactly 1 player.");
81
assert_eq!(
82
expected.mana,
83
actual.unwrap().mana,
84
"Player does not have expected starting mana."
85
);
86
}
87
88
#[test]
89
fn test_spell_casting() {
90
let mut app = create_test_app();
91
app.add_plugins(game_plugin);
92
93
// Simulate pressing space to trigger the spell casting system.
94
app.world_mut()
95
.resource_mut::<ButtonInput<KeyCode>>()
96
.press(KeyCode::Space);
97
// Allow the systems to recognize the input event.
98
app.update();
99
100
let expected = Player::default();
101
let actual = app
102
.world_mut()
103
.query::<&Player>()
104
.single(app.world())
105
.unwrap();
106
assert_eq!(
107
expected.mana - 1,
108
actual.mana,
109
"A single mana point should have been used."
110
);
111
112
// Clear the `just_pressed` status for all `KeyCode`s
113
app.world_mut()
114
.resource_mut::<ButtonInput<KeyCode>>()
115
.clear();
116
app.update();
117
118
// No extra spells have been cast, so no mana should have been used.
119
let after_keypress_event = app
120
.world_mut()
121
.query::<&Player>()
122
.single(app.world())
123
.unwrap();
124
assert_eq!(
125
expected.mana - 1,
126
after_keypress_event.mana,
127
"No further mana should have been used."
128
);
129
}
130
131
#[test]
132
fn test_window_title() {
133
let mut app = create_test_app();
134
app.add_plugins(game_plugin);
135
136
app.update();
137
138
let window = app
139
.world_mut()
140
.query::<&Window>()
141
.single(app.world())
142
.unwrap();
143
assert_eq!(window.title, "This is window 0!");
144
}
145
146