Automate Player Input with Unity Test Runner and NSubstitute
“How can I test player input in my Unity game…but without the need for a real human being?”
In this article, I’ll explain how to create input tests for a “Pong Clone” game using the .NET mocking library NSubstitute. All tests in this project run directly inside the Unity editor with Test Runner, and also on automation servers such as GitLab with GameCI. By reading this article, you’ll gain the knowledge required to simulate basic player input for test automation.
🏓 “Pong Clone” project reference on GitLab CI/CD 🏓
The above link will take you to this project’s home on GitLab, where you can view source code, and check out automated test results on the Pipelines page.
Let’s dive in!
It’s a Pong clone, but made with TDD 💻
This winter, I wanted to make a simple game in Unity using TDD (Test-driven Development) methodologies. Since my professional background is in test automation, I thought it would be fun to “virtually collaborate” with a YouTuber by letting them handle the gameplay code in the form of tutorial videos. With software development, the collaboration possibilities are vast and abundant. So why not try something new? By following Zigurous’s “How to make Pong in Unity” series, I could direct my efforts into writing and automating test cases instead of coding gameplay logic from the ground up.
Pong is a well known arcade classic, and relatively easy to recreate in Unity. While the full source code is provided on Zigurous’s GitHub, I found it beneficial to follow the videos and type in the C# scripts manually. This approach provided the opportunity to understand the objectives of each block of code, and also write tests at the same time. That said, there was a need to slightly modify the original player input code to properly facilitate testing. More on that in a bit!
Setting up NSubstitute for Unity ✅
Before we dive into the code, I should note that I used a Unity-specific version of NSubstitute, which is fully up-to-date with the mainline version, and also compatible with recent versions of the Unity editor. If you’re using this article as a technical reference, be sure to read the library’s setup instructions. New references need to be added to your assembly definition before running tests inside the Unity editor.
Finally, if you’re checking code into version control for the purpose of running tests on an automation server, no additional setup is required. Since the updated assembly references get carried over into version control, they’re equally available to the command line version of Unity used by GameCI. Simple as that!
Refactoring the PlayerInput script ♻️
Now that we’ve taken care of the setup specifics, let’s dive into the player input code. The developer’s original MonoBehavior combines player input with paddle movement inside PlayerPaddle.cs (GitHub). This works fine when we expect a human to move the player paddle up and down with a keyboard. However, if we want to automate player input for Unity test cases, we need to refactor by separating the input code from the paddle movement code. When we do this, both real and simulated input can interface with the code that moves the player paddle up and down.
Let’s begin by creating an interface called IPlayerInput. Define a method named VerticalDirection(), returning a Vector2 representative of the 🔼/🔽 arrow keys on a keyboard. We’ll be using this in just a moment.
Next, let’s reference our new IPlayerInput interface from the PlayerInput script to establish a contract between the two objects. This means that our PlayerInput class must define a VerticalDirection() method.
Line 2 references the IPlayerInput interface, and line 4 implements the required VerticalDirection() method seen in the first code snippet.
Refactoring the PlayerPaddle script ♻️
We’ll make another small modification in the interest of testing, this time to the PlayerPaddle script. But first, let’s better understand the relationship it has with our new, refactored PlayerInput script.
While looking at the PlayerInput class above, we can see Update() is called every frame, and checks if either the 🔼 or 🔽 arrow key is being pressed. When keyboard input is detected, the _verticalDirection property is set to Vector2.up or Vector2.down. Our VerticalDirection() method on line 4 returns this property when called from PlayerPaddle in FixedUpdate(), the script responsible for handling player paddle movement.
On line 4, notice how the PlayerInput() method is of the IPlayerInput type with a setter inside the body? This line is key, because it allows our player paddle tests to assign their own PlayerInput object, one that doesn’t rely on a human being pressing the 🔼/🔽 arrows on a keyboard.
Mocking PlayerPaddle’s input with substitution 🥸
With NSubstitute, we can “mock” the IPlayerInput interface containing the VerticalDirection() method. Let’s take a look at PlayerPaddleTests, and the UnitySetUp method SetupPlayerPaddle().
On line 13, we utilize NSubstitute’s Substitute.For<Interface>() method along with our IPlayerInput interface to assign a valid PlayerInput object to the test’s PlayerPaddle instance. Remember the setter method we defined in the PlayerPaddle class that makes this possible? As a result, our tests will soon be able to programmatically move the player’s paddle without any human intervention!
Simulating PlayerInput with Unity Test Runner 🕹️
Pong paddles have three states: moving down, moving up, and not moving at all. Each of these can be tested using Nsubstitute’s Returns() method by passing in the desired paddle direction. Let’s take a look at the code that checks if the player paddle moves upwards when our test instructs it to do so.
On line 9, the VerticalDirection property is set to Vector2.up. Because the PlayerPaddle script uses FixedUpdate() to continuously check for and apply movement in the vertical direction, the player paddle should move incrementally after a short wait of 0.1 seconds. After yielding on line 10, it’s time to make our assertions and pass or fail the test case. If the player paddle’s position on the Y axis is greater than 0, then we know it’s moving in the right direction — up! While not strictly necessary, I’ve included a couple extra assertions to ensure the paddle hasn’t moved on any additional axes unexpectedly.
Check out the full version of PlayerPaddle.cs on GitLab for additional tests where the paddle moves downward, and where the paddle doesn’t move at all.
Game over, the automation robots win? 🤖
In this article, we looked at simulating player input with Unity Test Runner. Since it’s not always desirable to have a human perform testing manually, using NSubstitute to mock player input inside a Unity test case is a relatively straightforward approach to automating player input. By separating player input from paddle movement, we were able to use common C# features including interfaces and property setters to our testing advantage.
How are you testing player input in your Unity project? Is it a manual process, or are you automating it? Let me know in the comments! 💬
Is your team hiring?
I’m Jordan, a Game Test Automation & Unity Tools Developer based in The Bay Area, California. 🌉 I can help your team achieve their test automation goals, improve code quality and increase developer satisfaction. Contact me directly! 📧