【问题标题】:Moq - Calling two functions in the same class, one real, one mockedMoq - 在同一个类中调用两个函数,一个是真实的,一个是模拟的
【发布时间】:2012-01-18 18:46:06
【问题描述】:

我有一段代码可能无法更改。理想情况下,我会将 Publish 方法移到另一个类中。模拟将成为一项微不足道的操作,我会很好。这是一个简化的示例。

public class Entity
{
  private IController _controller;

  // Omitted: Constructor where IController is assigned

  public virtual int Process()
  {
    return Publish(_controller.DoSomeStuff());
  }

  public virtual int Publish(int val)
  {
    Console.WriteLine(val);  // This is actually a call to an external system.
    return val;
  }
}

我想做的是调用真正的 Process 方法和模拟的 Publish 方法。 _controller 的值也会被模拟。

我尝试使用最小起订量并做:

var mock = new Mock<IController>();
mock.Setup(m => m.DoSomeStuff()).Returns(11);

var mock2 = new Mock<Entity_Accessor>();
mock2.CallBase = true;
mock2.Setup(a => a.Publish(It.IsAny<int>())).Returns(999);
mock2.Object._controller = mock.Object;

int result = mock2.Object.Process();
Assert.AreEqual(999, result);

当我运行这个测试时,返回值 11 而不是 999。如果我删除 mock2.CallBase = true,整个类都会被模拟,它会为每个函数返回默认值。

是否可以在这种情况下只模拟 Publish 方法,同时保留 Process 的原始实现?

是否可以使用不是 Typemock 或 Justmock 的不同模拟框架来实现上述目标(不确定客户是否会为模拟工具的多个副本买单)?

谢谢!

【问题讨论】:

    标签: c# unit-testing moq


    【解决方案1】:

    如果您尝试测试Process 方法的功能,您不应该使用模拟,您应该使用该类的真实实例并模拟IController 接口并模拟DoSomeStuff 以返回一个期望值,然后验证Process 是否正确处理该值。

    如果您只是想模拟您的 Entity 类,则无需模拟 IController。只需在 Entity 类上模拟您的方法并让它们返回您想要的。

    更新:

    您不需要模拟您正在测试的课程的一部分。也许您应该传入对外部系统的引用,以便您可以模拟它。除非 Publish 中的代码是直接调用外部系统的责任方。这将是推荐的方法。但是,如果 Publish 方法负责直接调用外部系统,那么您需要采取不同的方法并进行集成测试。

    与单元测试相比,集成测试通常不会运行得很快,并且它要求所有系统交互都启动并运行。这也意味着在大多数情况下,您不会嘲笑事物。这是野兽的本性,我强烈建议您不要绕过这样的过程。

    【讨论】:

    • 我列出的Process方法被大大简化了。在那里进行了大量的处理。我想做的只是测试 Process 方法。我想删除 Publish 方法,因为我不想发布我的测试消息。我已经可以模拟 IController 接口了。我不能做的是模拟 Publish 方法。
    【解决方案2】:

    由于您没有提到您的类是密封的(并且由于存在虚拟方法,我认为它不是),您应该考虑为这个特定场景创建它的可测试版本(模拟一个部分,保持另一个完整):

    public class TestableEntity : Entity
    {
        internal int PublishReturnValue { get; set; }
    
        public override int Publish(int value)
        {
            return PublishReturnValue;
        }
    }
    

    这样您仍然可以以常规方式测试Process 方法,但您可以模拟另一个:

    var controllerMock = new Mock<IController>();
    controllerMock.Setup(m => m.DoSomeStuff()).Returns(11);
    // assuming appropriate ctors
    var testableEntity = new TestableEntity(controllerMock);
    testableEntity.PublishReturnValue = 999;
    
    var result = testableEntity.Process();
    
    Assert.AreEqual(999, result);        
    

    请注意,需要以这种方式测试一个类,这可能表明需要进行一些重构工作。

    【讨论】:

    • 这是个好主意。如果我无法在计划中抽出时间进行重构,我可能会尝试这样做。
    • @user1156891:如果你有机会的话,一定要采纳 Charles Lambert 的建议(重构依赖项)。当您无法修改正在测试的代码时,我提出的解决方案(extract & override)通常很有用。祝你好运!
    猜你喜欢
    • 2012-07-26
    • 1970-01-01
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    相关资源
    最近更新 更多