2013-04-25 59 views
8

我试图用Moq来为实体框架代码优先类做一些测试。我对Moq和嘲笑技术很陌生,我想知道是否可以轻松地做一个我将在下面描述的测试。我通过网络搜索了一些解决方案,但大多数都基于存储库模式,我想避免这种情况。如何使用Moq与实体框架IDbSet Count()等方法

我有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; } 
} 

控制器和动作使用Moq的

测试

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

最后一个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来测试这个简单的动作,而不是使用完整的存储库模式,据我所知,无论如何这个.Count()部分硬编码成某种方法是可测试的......也许我只是以一种错误的方式使用模拟?因为我有这样的印象,即使用EF Code First,这应该是相当简单和可行的。

回答

14

如果你嘲讽测试实体不需要任何进一步的嘲笑环比下滑

像这样的东西应该做的(虽然,我不是在一个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

+0

注意:这不适用于异步查询。有关该解决方案的信息,请参阅官方的EF文档:http://msdn.microsoft.com/en-us/data/dn314429.aspx#async – 2014-12-04 03:05:09

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的一部分时,这是一个相当低的风险,如果你正在使用和编写你自己的扩展方法,请记住这一点。

+1

这适用于某些针对“IDbSet”的查询,但不是全部他们,例如'FirstOrDefault()',它不使用可疑的怪异。 – 2014-12-04 03:06:24