Question about testing and mocking with InputSystem

Hey Folks!

I’ve got question about writing a unit test involving interaction with the new unity input system.

Specifically as part of the unity open project, I thought I’d get to work with writing a unit test for this class (the input reader) https://github.com/UnityTechnologies/open-project-1/blob/master/UOP1_Project/Assets/Scripts/InputReader.cs#L34-L39

    public void OnAttack(InputAction.CallbackContext context)
    {
        if (attackEvent != null
            && context.phase == InputActionPhase.Started)
            attackEvent.Invoke();
    }

I thought this would be a good example test:

        [Test]
        public void InputAttack_CallsAttackFunction()
        {
            Mock<UnityAction> action = new Mock<UnityAction>();
            action.Setup(a => a.Invoke());
            inputReader.attackEvent += action.Object;
            // Can't mock a struct
            InputAction.CallbackContext context = new Mock<InputAction.CallbackContext>();
            /* NOT SHOWN: setup a getter on the mock that returns
               InputActionPhase.Started under context.phase */
            inputReader.OnAttack(context);
            action.Verify(a => a.Invoke(), Times.Once());
        }

If you look at the code it requires an InputAction.CallbackContext to be passed in. Unfortunately, InputAction.CallbackContext is a STRUCT which cannot be mocked.

I was wondering if anyone out there had some thoughts on how to test a function that takes an InputAction.CallbackContext

cc: @Rene-Damm , @StayTalm_Unity @cirocontinisio

Hi there, just passing by (zero knowledge about Unity, btw).

Supposing instances can’t be created then the only way is to extract an interface from the struct and then use it to create stubs (doubles that always return the same values). However that forces boxing/unboxing when passed as arguments, which can hit performance. The better way would be to allow instantiation of read-only structs (constructor-only arguments). In C# 9 you’ll have the record type which are the replacement for immutable structs along init modifiers. That way you can pass the mock to the stub struct.

As a sidenote, try not using mocking libraries in every test, they are pretty slow and add up quite fast for unit testing.

The new input system comes with an InputTestFixture that you can use to simulate inputs during tests.

That being said, them method doesn’t look like it’s very interesting to test. It’s just glues the input system to some UnityEvents, so a test for it is essentially “does the input system work”

2 Likes