 bb81a2cdb3
			
		
	
	
		bb81a2cdb3
		
			
		
	
	
	
	
		
			
			# Objective This older PR from `Wcubed` seemed well worth saving, adopted from #7314. See also tracking issue #2896 for ongoing discussion of Bevy testability. Thanks `Wcubed`! ## Solution - Updated for 0.15 - Added the `expected`/`actual` pattern - Switched to function plugin - Tweaked a bit of description ## Testing Green. --------- Co-authored-by: Wybe Westra <dev@wwestra.nl> Co-authored-by: Wybe Westra <wybe.westra@protonmail.com>
		
			
				
	
	
		
			133 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! Demonstrates simple integration testing of Bevy applications.
 | |
| //!
 | |
| //! By substituting [`DefaultPlugins`] with [`MinimalPlugins`], Bevy can run completely headless.
 | |
| //!
 | |
| //! The list of minimal plugins does not include things like window or input handling. The downside
 | |
| //! of this is that resources or entities associated with those systems (for example:
 | |
| //! `ButtonInput::<KeyCode>`) need to be manually added, either directly or via e.g.
 | |
| //! [`InputPlugin`]. The upside, however, is that the test has complete control over these
 | |
| //! resources, meaning we can fake user input, fake the window being moved around, and more.
 | |
| use bevy::prelude::*;
 | |
| 
 | |
| #[derive(Component)]
 | |
| struct Player {
 | |
|     mana: u32,
 | |
| }
 | |
| 
 | |
| impl Default for Player {
 | |
|     fn default() -> Self {
 | |
|         Self { mana: 10 }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Splitting a Bevy project into multiple smaller plugins can make it more testable. We can
 | |
| /// write tests for individual plugins in isolation, as well as for the entire project.
 | |
| fn game_plugin(app: &mut App) {
 | |
|     app.add_systems(Startup, (spawn_player, window_title_system).chain());
 | |
|     app.add_systems(Update, spell_casting);
 | |
| }
 | |
| 
 | |
| fn window_title_system(mut windows: Query<&mut Window>) {
 | |
|     for (index, mut window) in windows.iter_mut().enumerate() {
 | |
|         window.title = format!("This is window {index}!");
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn spawn_player(mut commands: Commands) {
 | |
|     commands.spawn(Player::default());
 | |
| }
 | |
| 
 | |
| fn spell_casting(mut player: Query<&mut Player>, keyboard_input: Res<ButtonInput<KeyCode>>) {
 | |
|     if keyboard_input.just_pressed(KeyCode::Space) {
 | |
|         let Ok(mut player) = player.get_single_mut() else {
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         if player.mana > 0 {
 | |
|             player.mana -= 1;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn create_test_app() -> App {
 | |
|     let mut app = App::new();
 | |
| 
 | |
|     // Note the use of `MinimalPlugins` instead of `DefaultPlugins`, as described above.
 | |
|     app.add_plugins(MinimalPlugins);
 | |
|     // Inserting a `KeyCode` input resource allows us to inject keyboard inputs, as if the user had
 | |
|     // pressed them.
 | |
|     app.insert_resource(ButtonInput::<KeyCode>::default());
 | |
| 
 | |
|     // Spawning a fake window allows testing systems that require a window.
 | |
|     app.world_mut().spawn(Window::default());
 | |
| 
 | |
|     app
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_player_spawn() {
 | |
|     let mut app = create_test_app();
 | |
|     app.add_plugins(game_plugin);
 | |
| 
 | |
|     // The `update` function needs to be called at least once for the startup
 | |
|     // systems to run.
 | |
|     app.update();
 | |
| 
 | |
|     // Now that the startup systems have run, we can check if the player has
 | |
|     // spawned as expected.
 | |
|     let expected = Player::default();
 | |
|     let actual = app.world_mut().query::<&Player>().get_single(app.world());
 | |
|     assert!(actual.is_ok(), "There should be exactly 1 player.");
 | |
|     assert_eq!(
 | |
|         expected.mana,
 | |
|         actual.unwrap().mana,
 | |
|         "Player does not have expected starting mana."
 | |
|     );
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_spell_casting() {
 | |
|     let mut app = create_test_app();
 | |
|     app.add_plugins(game_plugin);
 | |
| 
 | |
|     // Simulate pressing space to trigger the spell casting system.
 | |
|     app.world_mut()
 | |
|         .resource_mut::<ButtonInput<KeyCode>>()
 | |
|         .press(KeyCode::Space);
 | |
|     // Allow the systems to recognize the input event.
 | |
|     app.update();
 | |
| 
 | |
|     let expected = Player::default();
 | |
|     let actual = app.world_mut().query::<&Player>().single(app.world());
 | |
|     assert_eq!(
 | |
|         expected.mana - 1,
 | |
|         actual.mana,
 | |
|         "A single mana point should have been used."
 | |
|     );
 | |
| 
 | |
|     // Clear the `just_pressed` status for all `KeyCode`s
 | |
|     app.world_mut()
 | |
|         .resource_mut::<ButtonInput<KeyCode>>()
 | |
|         .clear();
 | |
|     app.update();
 | |
| 
 | |
|     // No extra spells have been cast, so no mana should have been used.
 | |
|     let after_keypress_event = app.world_mut().query::<&Player>().single(app.world());
 | |
|     assert_eq!(
 | |
|         expected.mana - 1,
 | |
|         after_keypress_event.mana,
 | |
|         "No further mana should have been used."
 | |
|     );
 | |
| }
 | |
| 
 | |
| #[test]
 | |
| fn test_window_title() {
 | |
|     let mut app = create_test_app();
 | |
|     app.add_plugins(game_plugin);
 | |
| 
 | |
|     app.update();
 | |
| 
 | |
|     let window = app.world_mut().query::<&Window>().single(app.world());
 | |
|     assert_eq!(window.title, "This is window 0!");
 | |
| }
 |