【问题标题】:How to use Moq with Entity Framework IDbSet Count() etc. methods如何将 Moq 与 Entity Framework IDbSet Count() 等方法一起使用
【发布时间】:2013-04-19 02:07:39
【问题描述】:

我正在尝试使用Moq 对实体框架代码优先类进行一些测试。我对 Moq 和模拟技术非常陌生,我想知道是否可以轻松地进行我将在下面描述的测试。我在网上搜索了一些解决方案,但大多数都是基于存储库模式,我想避免。

我有ITestEntities 上下文接口

public interface ITestEntities
{
    IDbSet<Order> Orders { get; }
    IDbSet<Product> Products { get; }
    IDbSet<User> Users { get; }
}

然后是上下文

public class TestEntities : DbContext, ITestEntities
{
    public TestEntities() : base("name=TestEntities")
    {

    }

    public virtual IDbSet<Order> Orders { get; set; }
    public virtual IDbSet<Product> Products { get; set; }
    public virtual IDbSet<User> Users { get; set; }
}

一个控制器和一个要测试的动作

public class HomeController : Controller
{
    private ITestEntities db;

    public HomeController()
    {
        db = new TestEntities();
    }

    public HomeController(ITestEntities db)
    {
        this.db = db;
    }

    public ActionResult Index()
    {
        var count = db.Users.Count();
        ViewBag.count = count;

        return View(count);
    }        
}

最后是使用 Moq 的 NUnit 测试

[Test]
public void ModelValueShouldBeTwo()
{
    var mockUsers = new Mock<IDbSet<User>>();
    mockUsers.Setup(m => m.Count()).Returns(2);

    var mockDB = new Mock<ITestEntities>();
    mockDB.Setup(db => db.Users).Returns((IDbSet<User>)mockUsers);

    var controller = new HomeController((ITestEntities)mockDB);

    var view = controller.Index();

    Assert.IsInstanceOf<ViewResult>(view);
    Assert.AreEqual(((ViewResult)view).Model, 2);
}

问题在于这一行:mockUsers.Setup(m =&gt; m.Count()).Returns(2);。运行此测试时出现以下错误:

System.NotSupportedException : Expression references a method that does not belong to the mocked object: m =&gt; m.Count&lt;User&gt;()

我认为这是因为 .Count() 是一个静态方法,所以不能被 Moq 模拟。有没有一种方法可以使用 Moq 而不是使用成熟的存储库模式来测试这个简单的操作,据我所知,无论如何都应该将这个 .Count() 部分硬编码为一些可测试的方法......也许我只是在一个错误道?因为我的印象是使用 EF Code First 应该非常简单且可行。

【问题讨论】:

    标签: asp.net-mvc entity-framework unit-testing moq


    【解决方案1】:

    如果您正在模拟测试实体,则无需进一步模拟链

    应该这样做(虽然,我不在 IDE 中,所以可能需要一些调整)

    更新以包含新的 InMemoryDbSet

    [Test]
    public void ModelValueShouldBeTwo()
    {
        //Build test users
        var mockUsers = new InMemoryDbSet<User>(){ new User(), new User()};
        var mockDB = new Mock<ITestEntities>();
        //Set up mock entities to returntest users.
        mockDB.Setup(db => db.Users).Returns(mockUsers);
    
        var controller = new HomeController((ITestEntities)mockDB);
    
        var view = controller.Index();
    
        Assert.IsInstanceOf<ViewResult>(view);
        Assert.AreEqual(((ViewResult)view).Model, 2);
    }
    

    这意味着扩展方法将简单地使用您提供的测试数据。

    请参阅下面有关模拟 dbset 的好文章 http://geekswithblogs.net/Aligned/archive/2012/12/12/mocking-or-faking-dbset.aspx

    【讨论】:

    【解决方案2】:

    模拟GetEnumerator() 而不是Count()

    Count()是实现IEnumerable&lt;T&gt;的对象的扩展方法,IDbSet&lt;T&gt;实现IEnumerable&lt;T&gt;

    扩展方法被传递给它们被调用的对象。在这种情况下,签名是:

    public static int Count<TSource>(
        this IEnumerable<TSource> source,  //This is your IDbSet that you are mocking
        Func<TSource, bool> predicate
    )
    

    您可以设置IEnumerable&lt;T&gt; 的成员来获得相同的结果,而不是尝试设置Count() 以返回特定值。对于IEnumerable&lt;T&gt;,您只需设置GetEnumerator() 以返回枚举两个值的Enumerator&lt;T&gt;

    在这种情况下,我通常通过创建一个包含几个项目的新列表并在其上调用 GetEnumerator() 来创建 Enumerator&lt;T&gt;

    mockUsers.Setup(m => m.GetEnumerator()).Returns(new List<Users> {
        new User(),
        new User()
    }.GetEnumerator());
    

    现在,除了您试图通过测试实现的任何目标之外,这当然有效地测试了扩展方法 Count(),而当扩展方法是 .NET 的一部分时,这是一个相当低的风险,它是请记住,如果您正在使用和创作自己的扩展方法。

    【讨论】:

    • 这适用于针对 IDbSet 的某些类型的查询,但不是全部,例如 FirstOrDefault(),它不奇怪地使用可枚举。
    猜你喜欢
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 2010-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-15
    • 1970-01-01
    相关资源
    最近更新 更多