You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 22:51:02