Search Unity

Question about testing and mocking with InputSystem

Discussion in 'Testing & Automation' started by alexchesser, Oct 19, 2020.

  1. alexchesser

    alexchesser

    Joined:
    Sep 15, 2017
    Posts:
    147
    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/UnityTechnologie...Project/Assets/Scripts/InputReader.cs#L34-L39

    Code (CSharp):
    1.  
    2.     public void OnAttack(InputAction.CallbackContext context)
    3.     {
    4.         if (attackEvent != null
    5.             && context.phase == InputActionPhase.Started)
    6.             attackEvent.Invoke();
    7.     }
    8.  
    I thought this would be a good example test:

    Code (CSharp):
    1.  
    2.         [Test]
    3.         public void InputAttack_CallsAttackFunction()
    4.         {
    5.             Mock<UnityAction> action = new Mock<UnityAction>();
    6.             action.Setup(a => a.Invoke());
    7.             inputReader.attackEvent += action.Object;
    8.             // Can't mock a struct
    9.             InputAction.CallbackContext context = new Mock<InputAction.CallbackContext>();
    10.             /* NOT SHOWN: setup a getter on the mock that returns
    11.                InputActionPhase.Started under context.phase */
    12.             inputReader.OnAttack(context);
    13.             action.Verify(a => a.Invoke(), Times.Once());
    14.         }
    15.  
    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
     
    Last edited: Oct 19, 2020
  2. reybrujo_

    reybrujo_

    Joined:
    Oct 2, 2020
    Posts:
    1
    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.
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    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"
     
    kirbygc00 likes this.