0

I'm a C# and Moq newb. I have some code that looks like the following and I want to unit test it, using Moq.

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

How do I set up the mocks? Current broken attempt:

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;

If I don't try to add the row, I get a NullReferenceException in the foreach. If I do try to add the row, I get a System.NotSupportedException: Type to mock must be an interface or an abstract or non-sealed class.

4

2 回答 2

1

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

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

于 2013-09-09T15:32:14.053 回答
1

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

在您上面的代码中,没有依赖项,因此 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实例,因为该方法依赖于它才能工作

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

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

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

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

(稍后,如果您想真正将其与该方法一起使用,则需要更改要实现的FooAdapter 类)IFooAdapterDoFooFooData

现在更改您的方法签名:

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);
    }

当然,如果您也需要 Mock ,您可以FooDataTable遵循做它应该做的事情(添加方法或其他)等等......当你确定的行为契约是好的时,你可以将它实现为一个具体的“存根”,然后你可以使用它代替...上下文中的任何引用,但现在您正在进入集成测试,这是另一天的故事...IFooAdapterIFooDataTableIFooDataTableFooDataTableIFooAdapter

于 2013-09-10T23:47:55.547 回答