如何使用Moq与Entity Framework IDbSet Count()等方法

时间:2013-04-25 08:50:19

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

我正在尝试使用Moq对Entity Framework Code First类进行一些测试。我对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 => m.Count()).Returns(2);。运行此测试时出现以下错误:

System.NotSupportedException : Expression references a method that does not belong to the mocked object: m => m.Count<User>()

我认为这是因为.Count()是一个静态方法所以它不能被Moq嘲笑。有没有办法使用Moq测试这个简单的操作,而不是使用完整的存储库模式,据我所知,无论如何应该将这个.Count()部分硬编码到一些可测试的方法中...也许我只是使用模拟以错误的方式?因为我觉得这应该非常简单并且可以使用EF Code First。

2 个答案:

答案 0 :(得分:16)

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

这样的事情应该做(虽然,我不是在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

答案 1 :(得分:1)

模拟GetEnumerator()而不是Count()

Count()是实施IEnumerable<T>的对象的扩展方法,IDbSet<T>实现IEnumerable<T>

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

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

您可以设置Count()的成员以获得相同的结果,而不是尝试设置IEnumerable<T>以返回特定值。在IEnumerable<T>的情况下,您只需设置GetEnumerator()即可返回枚举超过两个值的Enumerator<T>

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

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

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