【问题标题】:how to mock ITable with a concrete type如何用具体类型模拟 ITable
【发布时间】:2014-08-14 16:50:17
【问题描述】:

我正在为使用 System.Data.Linq DataConext 对象从数据库获取实体表的存储库编写单元测试。这是代码:

public class ForumRepository : IForumRepository
{
    protected Table<Forum> DataTable;
    IDataContextWrapper DataContext;

    public ForumRepository(IDataContextWrapper DataContext)
    {
        DataTable = DataContext.GetTable<Forum>();
    }

    public Forum GetForumById(int id)
    {
        try
        {
            return DataTable.Single(f => f.tblForumID.Equals(id));
        }
        catch(Exception e)
        {
            return null;
        }
    }

这是包装器的实现:

public class DataContextWrapper<T> : IDataContextWrapper where T : EpixForumDataContext, new()
{
    private readonly T db;

    public DataContextWrapper()
    {
        var t = typeof(T);
        db = (T)Activator.CreateInstance(t);
    }
    public DataContextWrapper(string connectionString)
    {
        var t = typeof(T);
        db = (T)Activator.CreateInstance(t, connectionString);
    }

    public Table<TableName> GetTable<TableName>() where TableName : class
    {
        return (Table<TableName>)db.GetTable(typeof(TableName));
    }

我想测试存储库方法。

public class UnitTest1
    {
        [TestMethod]
        public void Can_Get_Forum_ById()
        {
            //arrange
            Forum dummyForum = new Forum() { tblForumID = 1};
            Mock<ITable<Forum>> tableMock = new Mock<ITable<Forum>>();
            tableMock.Object.Attach(dummyForum);
            Mock<IDataContextWrapper> mock = new Mock<IDataContextWrapper>();
            mock.Setup(m => m.GetTable<Forum>()).Returns(tableMock.Object) ;

            //act
            ForumRepository repos = new ForumRepository(mock.Object);
            Forum resultForum = repos.GetForumById(1);

            //assert
            Assert.AreEqual(resultForum.tblForumID, 1);

Forum 是一个自动生成的类。我想为 Table 设置论坛,这样当我在 ContextWrapper 上执行 GetTable 时,我会得到论坛表。我不知道 Table.Attach 是否会将论坛附加到表格。另外,当我运行测试时,它会说

'要模拟的类型必须是接口或抽象类或非密封 类”。

难道我都搞错了吗?

【问题讨论】:

  • 这条线正确吗? Mock&lt;ITable&lt;TableName&gt;&gt; tableMock = new Mock&lt;ITable&lt;TableName&gt;&gt;(); ...我在您的代码中没有看到 TableName 类型
  • @pollirrata:很抱歉。它应该是 Mock。立即编辑问题

标签: c# linq unit-testing moq


【解决方案1】:

我发现您尝试执行的操作存在一些问题。

  1. 发布的代码无法编译

    IDataContextWrapper.GetTable 返回Table&lt;T&gt;,因此您不能将其设置为返回模拟的ITable&lt;T&gt;ITable 不是 Table,相反。这引出了我的下一点:

  2. IDataContextWrapper.GetTable 应该返回 ITable&lt;T&gt;,而不是 Table&lt;T&gt;

    这将让您模拟返回结果,因为 Table&lt;T&gt;sealed (Moq 无法模拟密封类,这可能是您收到您提到的错误的原因)。 program to interfaces 而不是混凝土,这也是很好的设计。

  3. 您不应该期望对Attach 的调用会做任何事情

    您正在尝试调用模拟接口上的方法,就像您期望它的行为好像已经实现了它一样。模拟的方法只会做你告诉他们的事情,所以在这种情况下(使用Loose behavior)它不会对那个调用做任何事情。相反,您应该设置您希望该表执行的操作,但这导致我:

  4. 您无法设置对Single 的调用,因为它是一种扩展方法

    Moq doesn't support setting up extension methods,因为它们是静态方法。但是,您可以设置对GetEnumerator 的调用,这是Single 无论如何调用的。不过,您需要mock the IQueryable&lt;T&gt; members,因为这才是Single 真正要发挥的作用。

所以,在解决了上面的第 1 点之后,你的测试最终应该是这样的:

[Test]
public void Can_Get_Forum_ById()
{
    // arrange
    Forum dummyForum = new Forum { tblForumID = 1 };
    IQueryable<Forum> forums = new List<Forum> { dummyForum }.AsQueryable();

    Mock<ITable<Forum>> tableMock = new Mock<ITable<Forum>>();
    tableMock.Setup(p => p.GetEnumerator()).Returns(forums.GetEnumerator());
    tableMock.Setup(r => r.Provider).Returns(forums.Provider);
    tableMock.Setup(r => r.ElementType).Returns(forums.ElementType);
    tableMock.Setup(r => r.Expression).Returns(forums.Expression);

    Mock<IDataContextWrapper> mock = new Mock<IDataContextWrapper>();
    mock.Setup(m => m.GetTable<Forum>()).Returns(tableMock.Object);

    // act
    ForumRepository repos = new ForumRepository(mock.Object);
    Forum resultForum = repos.GetForumById(1);

    // assert
    Assert.AreEqual(resultForum.tblForumID, 1);
}

请注意,这是一个很好的测试,而不是很好的测试。您可以用对First 的调用替换对Single 的调用,它仍然会通过,但总的来说显然是错误的。您至少应该添加此测试的否定,即如果 Id 不匹配,则不返回任何对象。

【讨论】:

  • 感谢帕特里克提供了很好的解释。但是,我不明白一件事。那张桌子不是Itable,而是相反。据我所知,如果狗类实现了 Animal 接口,那么狗是动物,但动物不是狗。
  • 这一行mock.Setup(m =&gt; m.GetTable&lt;Forum&gt;()).Returns(tableMock.Object); 给出了同样的无效参数错误。 Test Name: Can_Get_Forum_ById Result Message: Test method EpixForum.WebUI.Tests.UnitTest1.Can_Get_Forum_ById threw exception: System.NotSupportedException: Type to mock must be an interface or an abstract or non-sealed class. Result StackTrace: at Moq.Extensions.ThrowIfNotMockeable(Type typeToMock) at Moq.Mock1.CheckParameters() at Moq.Mock1..ctor(MockBehavior behavior, Object[] args) at Moq.Mock1..ctor() at EpixForum.WebUI.Tests.UnitTest1.Can_Get_Forum_ById()`
  • @user3527975 你是对的,我搞砸了哪个“不是”另一个,在编辑中修复。代码为我编译并运行。您是否将GetTable 的返回类型更改为ITable&lt;T&gt;(第2 点),而tableMockITable&lt;Forum&gt; 的模拟?
  • 是的。 IDataContextWrapper.GetTable 在我的代码中返回一个 ITablenamespace EpixForum.Domain.Abstract { public interface IDataContextWrapper { ITable&lt;T&gt; GetTable&lt;T&gt;() where T : class ; } }
  • @user3527975 该错误表示您正在尝试模拟密封类,Table 是。 tableMock 是什么类型?它应该是Mock&lt;ITable&lt;Forum&gt;&gt;,而不是Mock&lt;Table&lt;Forum&gt;&gt;。它也在 Mock 构造函数中被抛出,而不是在设置返回值的行上。
【解决方案2】:

试试这样的

Forum dummyForum = new Forum() { tblForumID = 1};
            Mock<ITable> tableMock = new Mock<ITable>();
            tableMock.Object.Attach(dummyForum);
            Mock<IDataContextWrapper> contextMock = new Mock<IDataContextWrapper>();
            contextMock .Setup(m => m.GetTable<Forum>()).Returns((ITable<Forum>)tableMock.Object) ;

            //act
            ForumRepository repos = new ForumRepository(mock.Object);
            Forum resultForum = repos.GetForumById(1);

            //assert
            Assert.AreEqual(resultForum.tblForumID, 1);

【讨论】:

  • 好的,这解决了我的一个问题。我在contextMock .Setup(m =&gt; m.GetTable&lt;Forum ()).Returns(tableMock.Object) ; 行仍然收到一个错误,说 Returns 方法的参数无效。对此有任何想法。我正在返回一个表,上下文包装器也应该返回。
  • 我相信是因为 tableMock.Object 是 object 类型的,你期望得到一个 Table。也许你需要投tableMock.Object,我更新了答案
  • 我确实尝试将对象投射到表格中。它仍然给出同样的错误。这条线是否正确 - tableMock.Object.Attach(dummyForum) ?由于 Table.Attach 将实体附加到数据上下文。 link
  • 有时编译器对 Moq 中的 Returns 调用感到困惑,无法确定正确的类型。尝试具体设置 -- Returns&lt;ITable&gt;(/* snip */
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-25
  • 2016-02-14
相关资源
最近更新 更多