【问题标题】:How to Mock with Moq to do foreach row in table如何模拟最小起订量以在表中执行 foreach 行
【发布时间】:2013-09-11 07:47:55
【问题描述】:

我是 C# 和 Moq 新手。我有一些如下所示的代码,我想使用 Moq 对其进行单元测试。

Data.Foo.FooDataTable tbl = Adapter.GetFooByID(id);
foreach (Data.Foo.FooRow row in tbl)
{
    x = row.bar
    ...
}

如何设置模拟?当前失败的尝试:

var adapter = new Mock<FooTableAdapter>();
var table = new Mock<Foo.FooDataTable>();
var rows = new Mock<DataRowCollection>();
var row = new Mock<Foo.FooRow>();
rows.Setup(x => x.GetEnumerator().Current).Returns(row.Object);
table.Setup(x => x.Rows).Returns(rows.Object);
adapter.Setup(x => x.GetFooByID(1)).Returns(table.Object);
_adapter = adapter.Object;

如果我不尝试添加行,我会在 foreach 中得到 NullReferenceException。如果我确实尝试添加该行,则会收到 System.NotSupportedException:要模拟的类型必须是接口或抽象或非密封类。

【问题讨论】:

    标签: c# mocking


    【解决方案1】:

    Mocks 非常棒,但它们确实是最后的测试工具——当你有一些不可能创建的对象时,你无法避免依赖——比如 HttpContext。

    在这种情况下,您可能不想创建 DataTable 的 moq 模拟 - 您可以使用适当的数据新建一个模拟。您想要 moq 模拟的是调用 Adapter.GetFooById() 以返回数据表的测试替身。

    【讨论】:

      【解决方案2】:

      只有当您想要测试需要所述依赖项的行为,但您不希望(或不能)实际创建该依赖项的“真实”实例时,才应使用模拟来创建假依赖项。任何包含多个模拟的测试方法都朝着错误的方向前进,因为这表明您有太多依赖项,或者您正在测试太多不相关的东西。

      在您上面的代码中,没有依赖项,因此 Mocks 并不适合您真正需要的。

      您确实需要考虑 究竟是您要在这里测试。为了争论,我们假设您显示的代码来自一个方法:

      public class MyFooClass
      {
         public int DoFooFooData(FooAdapter Foo)
         {
           Data.Foo.FooDataTable tbl = Adapter.GetFooByID(id);
           //just imagining what you might do here.
           int total=0;
           foreach (Data.Foo.FooRow row in tbl)
           {
             x = row.bar
             //just imagining what you might do here.
             total+=x;
          }
          return total;
        }
      }
      
      • 现在,让我们进一步假设您要对此方法进行单元测试。在这种情况下,为了调用该方法,您必须提供一个有效的 FooAdapter 实例,因为该方法依赖于它才能工作

      • 但是现在让我们假设您目前没有FooAdapter,因为它不存在,或者您可能无法提供一个,因为FooAdapter 建立了一个数据库连接,这是一个禁止-没有在单元测试中。

      为了测试DoFooFooData,我们需要做的是提供一个假的(Mock)FooAdapter,它只实现了GetFooByID 方法,以便您的函数能够执行。

      为此,您必须将 FooAdapter 抽象化或(我建议)通过接口声明它:

      public interface IFooAdapter { Data.Foo.FooDataTable GetByID(int id); }

      (稍后您需要更改 FooAdapter class 以实现 IFooAdapter,如果您想实际使用 DoFooFooData 方法)

      现在更改您的方法签名:

      public void DoFooFooData(IFooAdapter Foo)
          {
             Data.Foo.FooDataTable tbl = Adapter.GetFooByID(id);
               int total=0;
               foreach (Data.Foo.FooRow row in tbl)
               {
                 x = row.bar
                 //just imagining what you might do here
                 total+=x;
              }
              return total;
          }
      

      最后在你的测试方法中,你可以模拟这个依赖:

       void DoFooFooData_DoesSomeFooAndReturns3()
          {
            var mock = new Mock<IFooAdapter>();
            var table = new Data.Foo.FooDataTable();
            table.Add(new Data.Foo.FowRow{bar=1});
            table.Add(new Data.Foo.FowRow{bar=2});
            mock.Setup(m=>m.GetByID(It.IsAny<int>()).Returns(table);
      
            var sut = new MyFooClass();
            var expected=3;
            var actual=sut.DoFooFooData(mock.Object);
            Assert.AreEqual(expected,actual);
          }
      

      当然,如果您还需要模拟 FooDataTable,您可以遵循与 IFooAdapter 相同的模式,但您需要在这一点上停下来问问自己是否不应该创建一个 单独的测试,您在其中模拟 IFooDataTable 并确保它执行它应该做的事情(添加方法或其他)等等......当您确定 @ 的行为契约时987654338@ 可以,然后您可以将其实现为一个具体的“存根”,然后您可以在IFooAdapter 的上下文中使用它来代替任何FooDataTable 引用......但是现在您正在进入集成测试是另一天的故事......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-11-17
        • 2010-10-13
        • 2011-01-25
        • 1970-01-01
        • 1970-01-01
        • 2021-08-22
        • 2022-10-13
        相关资源
        最近更新 更多