【问题标题】:Mock a method of class under test with Moq & AutoMock使用 Moq 和 AutoMock 模拟被测类的方法
【发布时间】:2021-10-10 10:04:44
【问题描述】:

我一直在阅读,似乎找不到与我想要的 Moq 行为相匹配的案例。 我想模拟要测试的类的特定方法(与外部 API 接口)。问题是,据我了解,一旦类为.Create<T>,您就不能在其上使用.Setup()... 任何方法。

这是一个例子:

public class ClassA
{
   public void ConfigureExpansion()
   {
         var classB = new ClassB();

         var data = GetExternalData(); // I want to mock this
         //data is used in the code

         classB.TaskB(data); // I want to mock this
   }

   public string GetExternalData()
   {
         return data...
   }
}

public class ClassB
{
   public void TaskB(String myData)
   {
         //Does some work
   }
}
[TestMethod]
public void Test()
{
   using (var mock = AutoMock.GetLoose())
   {
      mock.Mock<IClassB>().Setup(x => x.TaskB(It.IsAny<string>()));
                     

      var cls = mock.Create<ClassA>();
      cls.Setup(x => x.GetExternalData(It.IsAny<string>())).Return("MyData");   // <--- This is not valid, but represents what I am trying to do.

      //act
      cls.ConfigureExpansion();

      mock.Mock<IClassB>().Verify(x => x.TaskB(), Times.Once);
   }
}

我试图为我的测试类的接口设置一个模拟,但cls 不会调用它:

[TestMethod]
public void Test()
{
   using (var mock = AutoMock.GetLoose())
   {
      mock.Mock<IClassA>().Setup(x => x.GetExternalData(It.IsAny<string>())).Return("MyData"); //<--- Mocking the method here is not detected by the cls below

      mock.Mock<IClassB>().Setup(x => x.TaskB(It.IsAny<string>()));             

      var cls = mock.Create<ClassA>();

      //act
      cls.ConfigureExpansion();

      mock.Mock<IClassB>().Verify(x => x.TaskB(), Times.Once);
   }
}

我也尝试过实例化我的被测类,当我这样做时,它不会考虑模拟(ClassB):

[TestMethod]
public void Test()
{
   using (var mock = AutoMock.GetLoose())
   {
      mock.Mock<IClassA>().Setup(x => x.GetExternalData(It.IsAny<string>())).Return("MyData"); //<--- Mocking the method here is not detected by the cls below

      mock.Mock<IClassB>().Setup(x => x.TaskB(It.IsAny<string>()));             

      var cls = new ClassA();

      //act
      cls.ConfigureExpansion();

      mock.Mock<IClassB>().Verify(x => x.TaskB(), Times.Once);
   }
}

我非常感谢您对此的任何见解, 谢谢

【问题讨论】:

  • 如果cls(或ClassA)是你的被测类,那么你应该使用它的构造函数来创建,而不是它的模拟版本。 IClassBClassA 之间有什么关系?它是ClassA 的依赖项吗?
  • 我可能会误解 .Create 的目的。我认为这就是你应该如何实例化你的测试类。关于 IClassB 和 ClassA,关系类似于数据库接口类,它包含所有查询,我想在这种情况下模拟这些查询。 ClassA 利用 ClassB 获取数据。
  • @Phyziik 你能否用要测试的代码来扩展你的问题?我什至不确定您的测试的哪一行应该被视为 Act 阶段。你想评估什么?
  • @PeterCsala 你完全正确,我的问题是缺少代码上下文。我彻底检查了问题的代码部分。希望它更清楚。对不起。
  • @Phyziik 感谢您分享这段代码。问题是你不能像这样嘲笑classBClassA 应该在其构造函数中接收一个 IClassB 实例以便能够模拟它。此外,如果您想模拟 GetExternalData,那么您应该使用模拟的 CallBase 功能来完成。如果您希望我可以留下一个帖子来更详细地解释这两个概念

标签: c# unit-testing mocking moq autofac


【解决方案1】:

这是解决您的问题的一种方法。如果要测试ConfigureExpansionClassA,则必须模拟该方法中使用的依赖项,即ClassB。你不能模拟ClassAGetExternalData,因为ClassA 是现在正在测试的类。您可能必须以某种方式重构您的类,以便有一个 ClassUnderTest 使用依赖注入创建 ClassAClassB 的两个实例。然后,您可以使用 Moq 模拟接口IClassAIClassB 来测试ConfigureExpansion

public class ClassUnderTest
{
    private readonly IClassA _classA;
    private readonly IClassB _classB;

    public ClassUnderTest(IClassA classA, IClassB classB)
    {
        this._classA = classA;
        this._classB = classB;
    }

    public void ConfigureExpansion()
    {
        var data = this._classA.GetExternalData();
        this._classB.TaskB(data);
    }
}


public interface IClassA
{
    string GetExternalData();
}

public class ClassA : IClassA
{
    public string GetExternalData()
    {
        return "some data";
    }
}

public interface IClassB
{
    void TaskB(string myData);
}

public class ClassB : IClassB
{
    public void TaskB(string myData)
    {
        //Does some work
    }
}

测试方法可以是:

    [TestMethod]
    public void Test()
    {
        using (var mock = AutoMock.GetLoose())
        {
            var mockClassA = mock.Mock<IClassA>();
            var mockClassB = mock.Mock<IClassB>();
            mockClassA.Setup(x => x.GetExternalData()).Returns("MyData");

            var cls = new ClassUnderTest(mockClassA.Object, mockClassB.Object);

            //act
            cls.ConfigureExpansion();

           mockClassB.Verify(x => x.TaskB(It.IsAny<string>()), Times.Once);
        }
    }

【讨论】:

  • 我不是投反对票的人,但我最好的猜测是,这是因为您完全忽略了 AutoMock。但这只是一个有根据的猜测。
  • @PeterCsala 我仍然在测试方法示例中使用 AutoMock 作为 OP 使用。
  • 您手工制作了被测系统(本例中为cls 变量),而不是通过调用mock.Create&lt;ClassUnderTest&gt;()
  • 我明白你关于重构的观点。我只是觉得我可能会更频繁地遇到这个问题,所以我试图找出一种方法来处理这种情况。不过感谢您的想法!
  • 添加到@PeterCsala 的评论:var cls = new ClassUnderTest(mockClassA.Object, mockClassB.Object); 不使用依赖注入。这是一个..不寻常的..使用Autofac。我的理解是,给定一个 DI 框架,当你得到一个创建测试对象的语句时,你应该能够简单地执行var cls = mock.Create&lt;ClassUnderTest&gt;();。前面的语句将对其进行设置,以便将所需的依赖项 IClassAIClassB 自动创建为模拟对象。 (但谁知道呢——也许这种技术在某些高级情况下很有用。)
猜你喜欢
  • 1970-01-01
  • 2014-02-23
  • 2010-11-12
  • 2020-02-18
  • 1970-01-01
  • 2014-01-18
  • 2014-08-17
  • 2011-01-18
相关资源
最近更新 更多