A game about forced loneliness, made by TACStudios
1---
2uid: input-system-testing
3---
4# Input testing
5
6The Input System has built-in support for writing automated input tests. You can drive input entirely from code, without any dependencies on platform backends and physical hardware devices. The automated input tests you write consider the generated input to be the same as input generated at runtime by actual platform code.
7
8## Setting up test assemblies
9
10To set up a test assembly that uses the Input System's automation framework, follow these steps:
11
121. In the `Packages/manifest.json` file of your project, `com.unity.inputsystem` must be listed in `testables`. This is necessary for test code that comes with the package to be included with test builds of your project.<br><br>You can, for example, add this after the `dependencies` property like so:
13 ```
14 },
15 "testables" : [
16 "com.unity.inputsystem"
17 ]
18 ```
192. Create a new assembly definition (menu: __Create > Assembly Definition__) or go to an assembly definition for a test assembly that you have already created.
203. Add references to `nunit.framework.dll`, `UnityEngine.TestRunner`, and `UnityEditor.TestRunner` (as described in [How to create a new test assembly](https://docs.unity3d.com/Packages/com.unity.test-framework@1.0/manual/workflow-create-test-assembly.html)), as well as `Unity.InputSystem` and `Unity.InputSystem.TestFramework` for the Input System.
21
22
23
24## Setting up test fixtures
25
26Use [`InputTestFixture`](../api/UnityEngine.InputSystem.InputTestFixture.html) to create an isolated version of the Input System for tests. The fixture sets up a blank, default-initialized version of the Input System for each test, and restores the Input System to its original state after the test completes. The default-initialized version has all built-in registrations (such as layout and processors), but doesn't have any pre-existing Input Devices.
27
28>__NOTE:__ [`InputTestFixture`](../api/UnityEngine.InputSystem.InputTestFixture.html) will not have custom registrations performed from Unity startup code such as `[InitializeOnLoad]` or `[RuntimeInitializeOnLoadMethod]`. Layouts needed during tests have to be manually registered as part of the test setup.
29
30You can use the fixture as a base class for your own fixture:
31
32```CSharp
33class MyTests : InputTestFixture
34{
35 [Test]
36 public void CanPressButtonOnGamepad()
37 {
38 var gamepad = InputSystem.AddDevice<Gamepad>();
39 Press(gamepad.buttonSouth);
40 }
41
42 // If you need custom setup and tear-down logic, override the methods inherited
43 // from InputTestFixture.
44 // IMPORTANT: If you use NUnit's [Setup] and [TearDown] attributes on methods in your
45 // test fixture, this will *override* the methods inherited from
46 // InputTestFixture and thus cause them to not get executed. Either
47 // override the methods as illustrated here or call the Setup() and
48 // TearDown() methods of InputTestFixture explicitly.
49 public override void Setup()
50 {
51 base.Setup();
52 // Add setup code here.
53 }
54 public override void TearDown()
55 {
56 // Add teardown code here.
57 base.TearDown();
58 }
59}
60```
61
62>__IMPORTANT:__ If you do this, do __not__ add a `[SetUp]` or `[TearDown]` method. Doing so will cause the methods in [`InputTestFixture`](../api/UnityEngine.InputSystem.InputTestFixture.html) to not be called, thus leading to the test fixture not properly initializing or shutting down. Instead, override the `Setup` and/or `TearDown` method inherited from `InputTestFixture`.
63
64Alternatively, you can instantiate it in your fixture:
65
66```CSharp
67[TestFixture]
68class MyTestFixture
69{
70 private InputTestFixture input = new InputTestFixture();
71
72 // NOTE: You have to manually call Setup() and TearDown() in this scenario.
73
74 [SetUp]
75 void Setup()
76 {
77 input.Setup();
78 }
79
80 [TearDown]
81 void TearDown()
82 {
83 input.TearDown();
84 }
85}
86```
87
88This is especially useful when creating a larger setup for game testing using `PrebuiltSetup`.
89
90```CSharp
91[PrebuildSetup("GameTestPrebuildSetup")]
92public class GameTestFixture
93{
94 public Game game { get; set; }
95 public InputTestFixture input { get; set; }
96
97 public Mouse mouse { get; set; }
98 public Keyboard keyboard { get; set; }
99 public Touchscreen touchscreen { get; set; }
100 public Gamepad gamepad { get; set; }
101
102 //...
103}
104
105#if UNITY_EDITOR
106public class GameTestPrebuildSetup : IPrebuildSetup
107{
108 public void Setup()
109 {
110 UnityEditor.EditorBuildSettings.scenes = new[]
111 {
112 new UnityEditor.EditorBuildSettingsScene("Assets/Scenes/Main.unity", true)
113 };
114 }
115}
116#endif
117```
118
119Note that you do __not__ generally need to clean up any input-related data you set up. This includes devices you add, layouts you registered, [`InputSettings`](../api/UnityEngine.InputSystem.InputSystem.html#UnityEngine_InputSystem_InputSystem_settings) you modify, and any other alteration to the state of [`InputSystem`](../api/UnityEngine.InputSystem.InputSystem.html). [`InputTestFixture`](../api/UnityEngine.InputSystem.InputTestFixture.html) will automatically throw away the current state of the Input System and restore the state from before the test was started.
120
121## Writing tests
122
123When writing a test, use [`InputSystem.AddDevice<T>()`](../api/UnityEngine.InputSystem.InputSystem.html#UnityEngine_InputSystem_InputSystem_AddDevice__1_System_String_) to add new Devices.
124
125```CSharp
126 [Test]
127 public void PlayerInput_CanInstantiatePlayer_WithSpecificControlScheme()
128 {
129 InputSystem.AddDevice<Gamepad>();
130 var keyboard = InputSystem.AddDevice<Keyboard>();
131 var mouse = InputSystem.AddDevice<Mouse>();
132
133 var prefab = new GameObject();
134 prefab.SetActive(false);
135 var prefabPlayerInput = prefab.AddComponent<PlayerInput>();
136 prefabPlayerInput.actions = InputActionAsset.FromJson(kActions);
137
138 var player = PlayerInput.Instantiate(prefab, controlScheme: "Keyboard&Mouse");
139
140 Assert.That(player.devices, Is.EquivalentTo(new InputDevice[] { keyboard, mouse }));
141 Assert.That(player.controlScheme, Is.EqualTo("Keyboard&Mouse"));
142 }
143```
144
145To feed input, the easiest way is to use the [`Press(button)`](../api/UnityEngine.InputSystem.InputTestFixture.html#UnityEngine_InputSystem_InputTestFixture_Press_UnityEngine_InputSystem_Controls_ButtonControl_System_Double_System_Double_System_Boolean_), [`Release(button)`](../api/UnityEngine.InputSystem.InputTestFixture.html#UnityEngine_InputSystem_InputTestFixture_Release_UnityEngine_InputSystem_Controls_ButtonControl_System_Double_System_Double_System_Boolean_), [`PressAndRelease(button)`](../api/UnityEngine.InputSystem.InputTestFixture.html#UnityEngine_InputSystem_InputTestFixture_PressAndRelease_UnityEngine_InputSystem_Controls_ButtonControl_System_Double_System_Double_System_Boolean_), `Set(control,value)`, and [`Trigger(action)`](../api/UnityEngine.InputSystem.InputTestFixture.html#UnityEngine_InputSystem_InputTestFixture_Trigger_UnityEngine_InputSystem_InputAction_) helper methods provided by [`InputTestFixture`](../api/UnityEngine.InputSystem.InputTestFixture.html).
146
147```CSharp
148 [Test]
149 public void Actions_WhenDisabled_CancelAllStartedInteractions()
150 {
151 var gamepad = InputSystem.AddDevice<Gamepad>();
152
153 var action1 = new InputAction("action1", binding: "<Gamepad>/buttonSouth", interactions: "Hold");
154 var action2 = new InputAction("action2", binding: "<Gamepad>/leftStick");
155
156 action1.Enable();
157 action2.Enable();
158
159 Press(gamepad.buttonSouth);
160 Set(gamepad.leftStick, new Vector2(0.123f, 0.234f));
161
162 using (var trace = new InputActionTrace())
163 {
164 trace.SubscribeTo(action1);
165 trace.SubscribeTo(action2);
166
167 runtime.currentTime = 0.234f;
168 runtime.advanceTimeEachDynamicUpdate = 0;
169
170 action1.Disable();
171 action2.Disable();
172
173 var actions = trace.ToArray();
174
175 Assert.That(actions.Length, Is.EqualTo(2));
176 //...
177 }
178 }
179```
180
181Alternatively, you can use code to feed arbitrary input events into the system, and run arbitrary input updates:
182
183```CSharp
184 [Test]
185 public void PlayerInput_JoiningPlayerThroughButtonPress_WillFailIfDeviceIsNotUsableWithPlayerActions()
186 {
187 var playerPrefab = new GameObject();
188 playerPrefab.SetActive(false);
189 playerPrefab.AddComponent<PlayerInput>();
190 playerPrefab.GetComponent<PlayerInput>().actions = InputActionAsset.FromJson(kActions);
191
192 var manager = new GameObject();
193 var listener = manager.AddComponent<MessageListener>();
194 var managerComponent = manager.AddComponent<PlayerInputManager>();
195 managerComponent.joinBehavior = PlayerJoinBehavior.JoinPlayersWhenButtonIsPressed;
196 managerComponent.playerPrefab = playerPrefab;
197
198 // Create a Device based on the HID layout with a single button control.
199 const string kLayout = @"
200 {
201 ""name"" : ""TestDevice"",
202 ""extend"" : ""HID"",
203 ""controls"" : [
204 { ""name"" : ""button"", ""layout"" : ""Button"" }
205 ]
206 }
207 ";
208
209 InputSystem.RegisterLayout(kLayout);
210 var device = InputSystem.AddDevice("TestDevice");
211
212 using (StateEvent.From(device, out var eventPtr))
213 {
214 ((ButtonControl)device["button"]).WriteValueIntoEvent(1f, eventPtr);
215 InputSystem.QueueEvent(eventPtr);
216 InputSystem.Update();
217 }
218
219 Assert.That(listener.messages, Is.Empty);
220 Assert.That(PlayerInput.all, Is.Empty);
221 }
222```
223
224>__Note__: For reference, you can find the tests for the Input System itself in its [GitHub repository](https://github.com/Unity-Technologies/InputSystem/tree/stable/Assets/Tests/InputSystem).