In this lesson, you will learn how to verify your game logic in total isolation by leveraging dependency injection to swap real services for lightweight test mocks.
The Problem with Testing Unity Objects
When your game logic (like a ScoreManager or PlayerController) relies on complex Unity services—such as SceneManager, physics engines, or heavy ScriptableObject databases—unit testing becomes nearly impossible. If you try to run these tests in the Unity Editor, you often encounter "null reference" errors because the engine infrastructure isn't initialized, or the test runs too slowly because it’s loading actual assets.
The Solution: Interface-Based Injection
Dependency Injection (DI) allows us to break this coupling. Instead of letting your class "new up" a dependency or look it up via GameObject.Find(), you inject an interface. During production, the DI container (like VContainer or Zenject) provides the real service. During testing, you provide a "mock" or "fake" version of that interface that performs no engine work.
Consider a RewardService that rewards players based on a PlayerDatabase:
public class RewardService {
private IPlayerDatabase _database;
// Injecting the interface instead of a concrete class
public RewardService(IPlayerDatabase database) => _database = database;
public void AddGold(int amount) {
var player = _database.GetPlayerData();
player.Gold += amount;
}
}
Writing the Isolated Test
Because RewardService takes an IPlayerDatabase in its constructor, your unit test doesn’t need a database file or a Unity scene. You simply create a "Fake" class that implements IPlayerDatabase and return a dummy object:
[Test]
public void RewardService_AddsGoldCorrectly() {
// Arrange: Create a mock that returns a clean, predictable state
var mockDb = new FakePlayerDatabase();
var service = new RewardService(mockDb);
// Act
service.AddGold(10);
// Assert: Check the state of the mock without any Unity engine calls
Assert.AreEqual(10, mockDb.Player.Gold);
}
By decoupling your logic from the engine's overhead, your tests become lightning-fast, predictable, and—most importantly—executable outside of the Unity Editor environment.
Key Takeaway: By injecting interfaces, you transform untestable "black box" components into isolated modules that can be verified instantly without the overhead of the Unity engine.