1
\$\begingroup\$

My player has an Action in the Input System Move, that takes a Vector2 and moves the player based on that input. Straight-forward. I'm using the "Send Messages" behavior on my Player's PlayerInput component.

For the life of me, I can't figure out how to test if OnMove is being called effectively. Here's my most recent attempt, I've tried instantiating the player piecewise from a new game object, loading the player as a prefab, finding the player in the scene, doing any and all of these things in the test and in a setup test function, and I've gotten nowhere.

I have not even managed to get a Minimum Viable Example of this feature actually working. I'm sure I'm completely missing something, but don't know what it is.

The Error

Player_Should_Be_Able_To_Move (0.437s)
---
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/[email protected]/InputSystem/Plugins/Users/InputUser.cs:406)
UnityEngine.InputSystem.PlayerInput.StopListeningForUnpairedDeviceActivity () (at Library/PackageCache/[email protected]/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1686)
UnityEngine.InputSystem.PlayerInput.OnDisable () (at Library/PackageCache/[email protected]/InputSystem/Plugins/PlayerInput/PlayerInput.cs:1721)
---
ArgumentOutOfRangeException: Cannot be negative

As far as I can tell, there's no reference to a line of code in my files that would help me isolate the issue.

Test

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Core;
using FluentAssertions;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;

namespace Tests.PlayMode {
    public class PlayerTests {
        public class Physics {...}

        public class Animations {...}

        public class Movement : InputTestFixture {

            [UnityTest]
            public IEnumerator Player_Should_Be_Able_To_Move() {
                SceneManager.LoadScene("TestScene");
                yield return new WaitForSeconds(0.1f);
                
                InputSystem.RegisterLayout<Keyboard>();
                var keyboard = InputSystem.AddDevice<Keyboard>();

                var playerPrefabObject = Resources.Load("Player");
                var playerGameObject = (GameObject)Object.Instantiate(playerPrefabObject);
                var player = playerGameObject.GetComponent<Player>();
                
                yield return new WaitForSeconds(0.5f);
                player.Rigidbody.velocity.x.Should().Be(0);
                
                Press(keyboard.dKey);
                yield return new WaitForSeconds(0.1f);
                Debug.Log(player.MoveDir);
                Debug.Log(player.Rigidbody.velocity);
                player.Rigidbody.velocity.x.Should().BePositive();
                
                Release(keyboard.dKey);
            }
        }
    }
}

Player

using UnityEngine;
using UnityEngine.InputSystem;

namespace Core {
    public class Player : MonoBehaviour {
        public Rigidbody2D Rigidbody { get; private set; }
        public CapsuleCollider2D Collider { get; private set; }
        public Vector2 MoveDir { get; private set; }
    
        private void Start() {
            Rigidbody = GetComponent<Rigidbody2D>();
            Collider = GetComponent<CapsuleCollider2D>();
        }

        public void OnMove(InputValue value) {
            Debug.Log("OnMove called");
            MoveDir = value.Get<Vector2>();
            Rigidbody.velocity = MoveDir * 10f;
        }
    }
}
\$\endgroup\$
0

2 Answers 2

1
\$\begingroup\$

So, I think you're hitting a red herring with the error you're seeing. I'm unsure of exactly how Unity handles deleting objects in PlayMode tests if you don't manually control it with Object.Destroy in a TearDown step - my assumption is that it is disposed of when it goes out of scope of the test.

When I was hitting this error in my tests, it was because the Player GameObject was being destroyed after the input system had been torn down, which I assume was causing the input test framework to throw this device error.

My suggestion would be to try and manually control the object lifecycle:

public class Movement : InputTestFixture 
{
    private GameObject _player;

    public override void SetUp()
    { 
        base.SetUp();
        _player = Object.Instantiate(/* player prefab */)
    }        

    public override void TearDown()
    {
        Object.Destroy(_player)
        base.TearDown();
    }

    [UnityTest]
    public IEnumerator Player_Should_Be_Able_To_Move()
        // Removed for brevity
    }
}

If you need the WaitForSeconds and thereby need the UnitySetUp / UnityTearDown, you'll need to manually instantiate and handle the InputTestFixture instead of inheriting from it, as the order of operations is

  • UnitySetUp
  • SetUp
  • Test
  • TearDown
  • UnityTearDown

Alternatively, you could use LogAssert in any input tests that you write to either expect the error message or ignore all error messages, or but I would say this should only be a last resort, as you may end up hiding errors that would otherwise cause your tests to fail.

Resources:

\$\endgroup\$
0
\$\begingroup\$

The error mentions a parameter name "value". When you set MoveDir to value.Get(), MoveDir could be positive or negative. You then set Rigidbody.velocity to 10 times this. In Test, you say that the velocity should be positive, but it may be negative at times. This is what the error means by "cannot be negative". I hope this helps you fix your problem.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Can you clarify what would cause the velocity to be negative in this example, and how the code could be modified to avoid that situation? \$\endgroup\$
    – DMGregory
    Feb 1, 2022 at 3:31
  • \$\begingroup\$ I apologize, I seem to have not provided enough details, I should have shared my action mapping. The D key moves the player right, which in this case is the positive direction. Since it's the X component of the vector, there is no reason for it to be negative. \$\endgroup\$
    – JShoe
    Feb 1, 2022 at 4:48

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .