I cannot make Unity test-framework work with InputTestFixture

Hello! I’m currently doing some integration tests, and I’m failing to make my test run properly:

I’m on unity 2021.3.9f1, inputsystem: 1.4.2 and test-framework 2.0.1-exp.1

Here is the code:

using System.Collections;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;

namespace Tests
{
    [TestFixture]
    public class MovementComponentTest
    {
        private InputTestFixture _fixture = new InputTestFixture();
        private Keyboard _keyboard;

        private GameObject _sut;
        private Transform _transform;
        [UnitySetUp]
        public IEnumerator USetUp()
        {
            yield return SceneManager.LoadSceneAsync(
                "TestScene",
                new LoadSceneParameters(LoadSceneMode.Single, LocalPhysicsMode.Physics3D)
            );
        }

        [SetUp]
        public void SetUp()
        {
            _fixture.Setup();  
            _keyboard = InputSystem.AddDevice<Keyboard>();
           
            LoadTestPlayerPrefab();
        }
       
        [TearDown]
        public void TearDown()
        {
            _fixture.TearDown();  
        }

        [UnityTearDown]
        public void UTearDown()
        {
            Debug.Log("Tearing dow");
        }

        [UnityTest]
        public IEnumerator ShouldNotMoveWhenNothingIsPressed()
        {
            yield return new WaitForFixedUpdate();

            AssertPositionRotation(
                new Vector3(0, 0, 0),
                Quaternion.Euler(0, 0, 0)
            );
        }

        [UnityTest]
        public IEnumerator ShouldMoveForwardWhenTheCorrespondingInputIsPressed()
        {
            _fixture.Press(_keyboard.wKey);
            yield return new WaitForFixedUpdate();
            _fixture.Release(_keyboard.wKey);
           
            AssertPositionRotation(
                new Vector3(0, 0, Time.fixedDeltaTime),
                Quaternion.Euler(0, 0, 0)
            );
        }

        [UnityTest]
        public IEnumerator ShouldMoveBackwardsWhenTheCorrespondingInputIsPressed()
        {
            _fixture.Press(_keyboard.sKey);
            yield return new WaitForFixedUpdate();
            _fixture.Release(_keyboard.sKey);
           
            AssertPositionRotation(
                new Vector3(0, 0, -Time.fixedDeltaTime),
                Quaternion.Euler(0, 0, 0)
            );
        }
       
        private void AssertPositionRotation(
            in Vector3 position,
            in Quaternion rotation
        )
        {
            Assert.That(_transform.position, Is.EqualTo(position));
            Assert.That(_transform.rotation, Is.EqualTo(rotation));
        }

        private void LoadTestPlayerPrefab()
        {
            var prefab = AssetDatabase.LoadAssetAtPath(
                "Assets/Tests/TestPrefabs/TestPlayer.prefab",
                typeof(GameObject)
            );

            _sut = Object.Instantiate(prefab, new Vector3(0, 0, 0), Quaternion.identity) as GameObject;
            _transform = _sut.GetComponent<Transform>();
        }
    }
}

And here is the output I’m getting:

ShouldMoveForwardWhenTheCorrespondingInputIsPressed (0.501s)
---
SetUp : Unhandled log message: '[Exception] ArgumentOutOfRangeException: Cannot be negative
Parameter name: value'. Use UnityEngine.TestTools.LogAssert.Expect
---
UnityEngine.InputSystem.Users.InputUser.set_listenForUnpairedDeviceActivity (System.Int32 value) (at Library/PackageCache/com.unity.inputsystem@1.4.2/InputSystem/Plugins/Users/InputUser.cs:406)
UnityEngine.InputSystem.PlayerInput.StopListeningForUnpairedDeviceActivity () (at Library/PackageCache/com.unity.inputsystem@1.4.2/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1685)
UnityEngine.InputSystem.PlayerInput.OnDisable () (at Library/PackageCache/com.unity.inputsystem@1.4.2/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1720)
---
ArgumentOutOfRangeException: Cannot be negative
Parameter name: value
UnityEngine.InputSystem.Users.InputUser.set_listenForUnpairedDeviceActivity (System.Int32 value) (at Library/PackageCache/com.unity.inputsystem@1.4.2/InputSystem/Plugins/Users/InputUser.cs:406)
UnityEngine.InputSystem.PlayerInput.StopListeningForUnpairedDeviceActivity () (at Library/PackageCache/com.unity.inputsystem@1.4.2/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1685)
UnityEngine.InputSystem.PlayerInput.OnDisable () (at Library/PackageCache/com.unity.inputsystem@1.4.2/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1720)

Has anyone had the same issue? Am I doing something wrong with the Test-framework or the input system API? Thx in advance !

I had a similar issue which caused the same error message.

My solution was to destroy the GameObject which contains the PlayerInput before InputTestFixture TearDown.

So in your case, you would need to either unload the scene (note that TearDown is executed before UnityTearDown) or destroy _sut (depending on what contains PlayerInput components) before calling _fixture.TearDown()

Hi, did you ever find a solution to this issue? I’m experiencing the same exact problem.

I’ve tried destroying the PlayerInput gameObject as well as loading an empty scene in TearDown, but nothing seems to work. Does anyone know if there’s a simple working example of a test that uses the PlayerInput component in the loaded scene?

Thanks in advance.

Hope this helps someone in the future and save a few hours debugging.

I am using Unity 2022.3.48f1, and got this same error while writing PlayMode tests, relating to these error messages:

[Exception] ArgumentOutOfRangeException: Cannot be negative
Parameter name: value

UnityEngine.InputSystem.Users.InputUser.set_listenForUnpairedDeviceActivity

I found that if InputSystem.AddDevice<T>() is used to attach mouse and keyboard to the InputTestFixture, then in teardown, the fixture will attempt to remove the added devices and restore, and the original input devices.

I found the error message will appear in these 2 cases:

  1. If the UnityTest method encounters an exception and exits prematurely, then the teardown function cannot be run properly and the error message will happen.

  2. If there is a PlayerInput in the scene with Auto Switch enabled, then during teardown, for some reason I do not understand, the system tries to Auto Switch to a non existent device scheme and hence generates the invalid negative value error too.

The way to solve this problem is to disable Auto Switch, or, disable the PlayerInput component before teardown, so that PlayerInput doesn’t cause an attempt to auto switch to non-existent device.

Finally, as an aside, if teardown is not run properly due to any reasons, like a premature termination of the test case, then the Unity InputSystem in the Editor will be bugged out. The default static Keyboard or Mouse will not be available. In this case, closing and reopening the Unity Editor will fix the problem.