Skip to content
This repository has been archived by the owner on Jun 7, 2022. It is now read-only.

azriel91/amethyst_test_support

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Amethyst Test Support

Test harness to support testing of Amethyst types, including:

  • Bundle
  • State
  • System
  • Resource loading.
  • Arbitrary types that Systems use during processing.

The test harness minimizes boilerplate code to set up an Amethyst Application with common bundles, and can take in logic that is normally masked behind a number of layers through a thin interface.

Usage

The following shows an example of testing a State. More examples are in the Examples section.

extern crate amethyst;
extern crate amethyst_test_support;

use std::marker::PhantomData;

use amethyst_test_support::prelude::*;
use amethyst::{
    core::bundle::{self, SystemBundle},
    ecs::prelude::*,
    prelude::*,
};

#[derive(Debug)]
struct LoadResource;

struct LoadingState<'a, 'b, S>
where
    S: State<GameData<'a, 'b>> + 'static,
{
    next_state: Option<S>,
    state_data: PhantomData<State<GameData<'a, 'b>>>,
}
impl<'a, 'b, S> LoadingState<'a, 'b, S>
where
    S: State<GameData<'a, 'b>> + 'static,
{
    fn new(next_state: S) -> Self {
        LoadingState {
            next_state: Some(next_state),
            state_data: PhantomData,
        }
    }
}
impl<'a, 'b, S> State<GameData<'a, 'b>> for LoadingState<'a, 'b, S>
where
    S: State<GameData<'a, 'b>> + 'static,
{
    fn update(&mut self, data: StateData<GameData>) -> Trans<GameData<'a, 'b>> {
        data.data.update(&data.world);
        data.world.add_resource(LoadResource);
        Trans::Switch(Box::new(self.next_state.take().unwrap()))
    }
}

#[test]
fn assertion_push_with_loading_state_with_add_resource_succeeds() {
    let assertion = |world: &mut World| {
        world.read_resource::<LoadResource>();
    };

    assert!(
        AmethystApplication::blank()
            .with_state(|| LoadingState::new(EmptyState))
            .with_assertion(assertion)
            .run()
            .is_ok()
    );
}

The Amethyst application is initialized with one of the following functions, each providing a different level of default bundles:

extern crate amethyst_test_support;

use amethyst_test_support::prelude::*;

#[test]
fn test_name() {
    // Start with no bundles
    AmethystApplication::blank();

    // Start with the Transform, Input, and UI bundles
    AmethystApplication::base();

    // Start with the Animation, Transform, Input, UI, and Render bundles.
    let visibility = false; // Whether the window should be shown
    AmethystApplication::render_base("test_name", visibility);
}

Next, attach the logic you wish to test using the various .with_*(..) methods:

#[test]
fn test_name() {
    let effect = |world: &mut World| {
         let entity = world.create_entity().with(MyComponent(0)).build();

        // `EffectReturn` is a convenience wrapper struct to pass values between this effect
        // function and the assertion function.
        world.add_resource(EffectReturn(entity));
    };

    let assertion = |world: &mut World| {
        let entity = world.read_resource::<EffectReturn<Entity>>().0.clone();

        let my_component_storage = world.read_storage::<MyComponent>();
        let my_component = component_zero_storage
            .get(entity)
            .expect("Entity should have a `MyComponent` component.");

        // If "my_system" ran, the value in the `MyComponent` should be 1.
        assert_eq!(1, my_component.0);
    };

    let visibility = false; // Whether the window should be shown
    AmethystApplication::render_base("test_name", visibility)
        .with_bundle(MyBundle::new()) // Can be invoked multiple times.
        .with_bundle_fn(|| MyNonSendBundle::new()) // Can be invoked multiple times.
        .with_state(|| MyState::new()) // Must be before any calls to `.with_resource(R)`.
                                       // Can only be invoked once.
        .with_system(MySystem::new(), "my_system", &[]) // Can be invoked multiple times.
        .with_effect(effect) // Can only be invoked once.
        .with_assertion(assertion) // Can only be invoked once.
        .with_resource(MyResource::new()) // Adds a resource to the world.
                                          // Must be after any calls to `.with_state(S)`.
                                          // Can be invoked multiple times.
         // ...
}

Finally, call .run() to run the application. This returns amethyst::Result<()>, so you can wrap it in an assert!(..);:

#[test]
fn test_name() {
    let effect = // ...
    let assertion = // ...

    let visibility = false; // Whether the window should be shown
    assert!(
        AmethystApplication::render_base("test_name", visibility)
            // ...
            .with_effect(effect)
            .with_assertion(assertion)
            .run()
            .is_ok()
    );
}

Examples

Testing a bundle:

extern crate amethyst;
extern crate amethyst_test_support;

use amethyst_test_support::prelude::*;
use amethyst::{
    core::bundle::{self, SystemBundle},
    ecs::prelude::*,
    prelude::*,
};

#[derive(Debug)]
struct ApplicationResource;

#[derive(Debug)]
struct MySystem;
type MySystemData<'s> = ReadExpect<'s, ApplicationResource>;
impl<'s> System<'s> for MySystem {
    type SystemData = MySystemData<'s>;
    fn run(&mut self, _: Self::SystemData) {}
    fn setup(&mut self, res: &mut Resources) {
        MySystemData::setup(res);
        res.insert(ApplicationResource);
    }
}

#[derive(Debug)]
struct MyBundle;
impl<'a, 'b> SystemBundle<'a, 'b> for MyBundle {
    fn build(self, builder: &mut DispatcherBuilder<'a, 'b>) -> bundle::Result<()> {
        builder.add(MySystem, "my_system", &[]);
        Ok(())
    }
}

#[test]
fn bundle_registers_system_with_resource() {
    assert!(
        AmethystApplication::blank()
            .with_bundle(MyBundle)
            .with_assertion(|world| { world.read_resource::<ApplicationResource>(); })
            .run()
            .is_ok()
    );
}

Testing a system:

extern crate amethyst;
extern crate amethyst_test_support;

use amethyst_test_support::prelude::*;
use amethyst::{
    ecs::prelude::*,
    prelude::*,
};

struct MyComponent(pub i32);
impl Component for MyComponent {
    type Storage = DenseVecStorage<Self>;
}

#[derive(Debug)]
struct MySystem;
type MySystemData<'s> = WriteStorage<'s, MyComponent>;
impl<'s> System<'s> for MySystem {
    type SystemData = MySystemData<'s>;
    fn run(&mut self, mut my_component_storage: Self::SystemData) {
        for mut my_component in (&mut my_component_storage).join() {
            my_component.0 += 1
        }
    }
}

#[test]
fn system_increases_component_value_by_one() {
    let effect_fn = |world: &mut World| {
        let entity = world.create_entity().with(MyComponent(0)).build();

        world.add_resource(EffectReturn(entity));
    };
    let assertion = |world: &mut World| {
        let entity = world.read_resource::<EffectReturn<Entity>>().0.clone();

        let my_component_storage = world.read_storage::<MyComponent>();
        let my_component = my_component_storage
            .get(entity)
            .expect("Entity should have a `MyComponent` component.");

        // If the system ran, the value in the `MyComponent` should be 1.
        assert_eq!(1, my_component.0);
    };

    assert!(
        AmethystApplication::blank()
            .with_system(MySystem, "my_system", &[])
            .with_effect(effect_fn)
            .with_assertion(assertion)
            .run()
            .is_ok()
    );
}

About

Test library to ease testing types used in the Amethyst game engine

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages