Testing Input
The input system has built-in support for writing automated tests involving input. This makes it possible to drive input entirely from code without dependencies on platform backends and actual hardware devices. To the tests, the generated input will look identical to input generated at runtime by actual platform code.
Setting Up Test Assemblies
Using test support requires setting up a test assembly for your tests. To do so, create a new assembly definition ("Create >> Assembly Definition") and add references to nunit.framework.dll
, UnityEngine.TestRunner
, and UnityEditor.TestRunner
(as described here) as well as UnityEngine.Input.dll
and UnityEngine.Input.TestFramework.dll
for the input system.
Setting Up Test Fixtures
Use InputTestFixture
to create an isolated version of the input system for tests. The fixture will set up a blank, default-initialized version of the input system for each test and restore the prior input system state after completion of the test. The default-initialized version has all built-in layout, processor, etc. registrations but has no pre-existing input devices. In addition, the fixture uses a custom IInputRuntime
implementation (available from the runtime
property of the fixture) in place of NativeInputRuntime
.
The fixture can either be used as a base class for your own fixture:
class MyTests : InputTestFixture
{
[Test]
public void CanPressButtonOnGamepad()
{
var gamepad = InputSystem.AddDevice<Gamepad>();
Press(gamepad.buttonSouth);
}
}
Or it can be instantiated in your fixture:
[TestFixture]
class MyTestFixture
{
private InputTestFixture input = new InputTestFixture();
}
This is especially useful when creating a larger setup for game testing using PrebuiltSetup
.
[PrebuildSetup("GameTestPrebuildSetup")]
public class GameTestFixture
{
public Game game { get; set; }
public InputTestFixture input { get; set; }
public Mouse mouse { get; set; }
public Keyboard keyboard { get; set; }
public Touchscreen touchscreen { get; set; }
public Gamepad gamepad { get; set; }
//...
}
#if UNITY_EDITOR
public class GameTestPrebuildSetup : IPrebuildSetup
{
public void Setup()
{
UnityEditor.EditorBuildSettings.scenes = new[]
{
new UnityEditor.EditorBuildSettingsScene("Assets/Scenes/Main.unity", true)
};
}
}
#endif
Writing Tests
In tests, use InputSystem.AddDevice<T>()
to add new devices.
[Test]
public void PlayerInput_CanInstantiatePlayer_WithSpecificControlScheme()
{
InputSystem.AddDevice<Gamepad>();
var keyboard = InputSystem.AddDevice<Keyboard>();
var mouse = InputSystem.AddDevice<Mouse>();
var prefab = new GameObject();
prefab.SetActive(false);
var prefabPlayerInput = prefab.AddComponent<PlayerInput>();
prefabPlayerInput.actions = InputActionAsset.FromJson(kActions);
var player = PlayerInput.Instantiate(prefab, controlScheme: "Keyboard&Mouse");
Assert.That(player.devices, Is.EquivalentTo(new InputDevice[] { keyboard, mouse }));
Assert.That(player.controlScheme, Is.EqualTo("Keyboard&Mouse"));
}
To feed input, the easiest way is to use the Press(button)
, Release(button)
, PressAndRelease(button)
, Set(control,value)
, and Trigger(action)
helper functions provided by InputTestFixture
.
[Test]
public void Actions_WhenDisabled_CancelAllStartedInteractions()
{
var gamepad = InputSystem.AddDevice<Gamepad>();
var action1 = new InputAction("action1", binding: "<Gamepad>/buttonSouth", interactions: "Hold");
var action2 = new InputAction("action2", binding: "<Gamepad>/leftStick");
action1.Enable();
action2.Enable();
Press(gamepad.buttonSouth);
Set(gamepad.leftStick, new Vector2(0.123f, 0.234f));
using (var trace = new InputActionTrace())
{
trace.SubscribeTo(action1);
trace.SubscribeTo(action2);
runtime.currentTime = 0.234f;
runtime.advanceTimeEachDynamicUpdate = 0;
action1.Disable();
action2.Disable();
var actions = trace.ToArray();
Assert.That(actions.Length, Is.EqualTo(2));
//...
}
}
Alternatively, arbitrary input events can be fed into the system and arbitrary input updates can be run by code.
[Test]
public void PlayerInput_JoiningPlayerThroughButtonPress_WillFailIfDeviceIsNotUsableWithPlayerActions()
{
var playerPrefab = new GameObject();
playerPrefab.SetActive(false);
playerPrefab.AddComponent<PlayerInput>();
playerPrefab.GetComponent<PlayerInput>().actions = InputActionAsset.FromJson(kActions);
var manager = new GameObject();
var listener = manager.AddComponent<MessageListener>();
var managerComponent = manager.AddComponent<PlayerInputManager>();
managerComponent.joinBehavior = PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed;
managerComponent.playerPrefab = playerPrefab;
// Create a device based on the HID layout with a single button control.
const string kLayout = @"
{
""name"" : ""TestDevice"",
""extend"" : ""HID"",
""controls"" : [
{ ""name"" : ""button"", ""layout"" : ""Button"" }
]
}
";
InputSystem.RegisterLayout(kLayout);
var device = InputSystem.AddDevice("TestDevice");
using (StateEvent.From(device, out var eventPtr))
{
((ButtonControl)device["button"]).WriteValueIntoEvent(1f, eventPtr);
InputSystem.QueueEvent(eventPtr);
InputSystem.Update();
}
Assert.That(listener.messages, Is.Empty);
Assert.That(PlayerInput.all, Is.Empty);
}
NOTE: For reference, the tests for the input system itself can be found here.