Unity Play Mode测试中Moq模拟方法未触发的问题咨询
Unity中Moq模拟未自动生效的问题
我在Unity项目里用Moq做模拟测试,想让场景启动时自动调用模拟后的LoadRemoteCatalog方法,不用手动显式调用。
实际代码
public class MyAddressablesClass { public virtual IEnumerator LoadRemoteCatalog() { // My implementation here } } public class MyCode { public IEnumerator Start() { // Doing some stuff MyAddressablesClass myAddressablesInstance = new MyAddressablesClass(); yield return myAddressablesInstance.LoadRemoteCatalog(); // Doing some other stuff } }
测试代码
private IEnumerator GetFakeCatalog() { yield return null; } [UnitySetUp] public IEnumerator Setup() { Mock<MyAddressablesClass> mockedClass = new Mock<MyAddressablesClass>(); mockedClass.Setup(service => service.LoadRemoteCatalog()).Returns(GetFakeCatalog()); } [UnityTest] public IEnumerator TestThings() { yield return SceneManager.LoadSceneAsync("MyScene"); }
疑问
是否必须显式调用模拟方法才能触发其实现?为什么场景启动时实际代码里的LoadRemoteCatalog没自动用模拟实现?我想测试场景启动的完整流程,不想手动触发模拟方法。
问题原因与解决方案
核心原因
你的模拟未生效,是因为实际代码中直接通过new MyAddressablesClass()创建了实例,完全没有引用你在Setup中创建的Mock对象。Moq的模拟对象是独立的实例,不会自动替换代码中new出来的对象。
解决方案:解耦依赖,让代码引用Mock实例
要让模拟生效,必须让MyCode调用你创建的Mock对象,而非自己new的真实实例。推荐用以下两种方式实现:
1. 构造函数注入(最直接)
修改MyCode类,通过构造函数接收MyAddressablesClass实例,兼容正常运行时的默认逻辑:
public class MyCode { private readonly MyAddressablesClass _addressables; // 测试时传入Mock实例,正常运行时自动使用真实实例 public MyCode(MyAddressablesClass addressables = null) { _addressables = addressables ?? new MyAddressablesClass(); } public IEnumerator Start() { // Doing some stuff yield return _addressables.LoadRemoteCatalog(); // Doing some other stuff } }
调整测试代码,将Mock实例注入到场景中的MyCode组件:
private IEnumerator GetFakeCatalog() { yield return null; } private Mock<MyAddressablesClass> _mockedAddressables; [UnitySetUp] public IEnumerator Setup() { _mockedAddressables = new Mock<MyAddressablesClass>(); _mockedAddressables.Setup(service => service.LoadRemoteCatalog()).Returns(GetFakeCatalog()); } [UnityTest] public IEnumerator TestThings() { // 加载场景 yield return SceneManager.LoadSceneAsync("MyScene"); // 找到场景中的MyCode组件 var myCode = UnityEngine.Object.FindObjectOfType<MyCode>(); // 通过反射替换私有字段(因为字段是private) var field = typeof(MyCode).GetField("_addressables", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); field.SetValue(myCode, _mockedAddressables.Object); // 场景加载后Start会自动执行,此时会调用Mock的LoadRemoteCatalog方法 }
2. 工厂模式(适合复杂依赖场景)
如果需要更灵活的实例创建逻辑,可以用工厂模式解耦:
// 定义工厂接口 public interface IAddressablesFactory { MyAddressablesClass Create(); } // 真实环境的工厂实现 public class DefaultAddressablesFactory : IAddressablesFactory { public MyAddressablesClass Create() => new MyAddressablesClass(); } // 修改MyCode,依赖工厂而非直接创建实例 public class MyCode { private readonly IAddressablesFactory _factory; public MyCode(IAddressablesFactory factory = null) { _factory = factory ?? new DefaultAddressablesFactory(); } public IEnumerator Start() { var addressables = _factory.Create(); yield return addressables.LoadRemoteCatalog(); } }
测试时,Mock工厂返回你的模拟对象:
[UnitySetUp] public IEnumerator Setup() { // 创建Mock的Addressables实例 var mockAddressables = new Mock<MyAddressablesClass>(); mockAddressables.Setup(s => s.LoadRemoteCatalog()).Returns(GetFakeCatalog()); // 创建Mock工厂,让它返回上面的模拟实例 var mockFactory = new Mock<IAddressablesFactory>(); mockFactory.Setup(f => f.Create()).Returns(mockAddressables.Object); // 注入到MyCode中(方式同构造函数注入) }
关键结论
- 不需要显式调用模拟方法,只要实际代码调用了Mock对象的对应方法,就会触发你设置的模拟实现。
- Moq无法自动替换代码中
new出来的实例,必须通过依赖注入/工厂模式解耦,让代码引用你创建的Mock对象。 - 调整后,场景启动的完整流程会自动使用模拟的
LoadRemoteCatalog方法,无需手动触发。
内容的提问来源于stack exchange,提问作者P-ratt
相关产品推荐
相关产品推荐

