Reputation: 4559
I'm trying to use Moq
to make some tests for Entity Framework Code First classes. I'm very new to Moq and mocking techniques and I wonder if it's possible to easily do a test that I will describe below. I searched through the web for some solutions, but most are based on repository pattern, which I want to avoid.
I have ITestEntities
interface for context
public interface ITestEntities
{
IDbSet<Order> Orders { get; }
IDbSet<Product> Products { get; }
IDbSet<User> Users { get; }
}
Then context
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; }
}
A controller and an action to test
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);
}
}
And finally a NUnit test using Moq
[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);
}
The problem is with this line: mockUsers.Setup(m => m.Count()).Returns(2);
. When running this test I get following error:
System.NotSupportedException : Expression references a method that does not belong to the mocked object: m => m.Count<User>()
I think this is due to .Count()
being a static method so it cannot be mocked by Moq. Is there a way to test this simple action using Moq and not using full-fledged repository pattern, which as I understand should anyway have this .Count()
part hardcoded into some method to be testable... Maybe I just use the mocks in a wrong way? Because I have impression that this should be quite simple and possible with EF Code First.
Upvotes: 9
Views: 10269
Reputation: 7126
If you are mocking the test entities you don't need to mock any further down the chain
Something like this should do (although, I 'm not at an IDE so may need some tweaking)
Update to include new 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);
}
This will mean the extension methods will simply work off of the test data you have supplied.
See below for a good article on mocking dbset http://geekswithblogs.net/Aligned/archive/2012/12/12/mocking-or-faking-dbset.aspx
Upvotes: 16
Reputation: 2030
Mock GetEnumerator()
instead of Count()
Count()
is an extension method on objects that implement IEnumerable<T>
, and IDbSet<T>
implements IEnumerable<T>
Extension methods are passed the object that they are called on. In this case the signature is:
public static int Count<TSource>(
this IEnumerable<TSource> source, //This is your IDbSet that you are mocking
Func<TSource, bool> predicate
)
Rather than trying to setup Count()
to return a specific value, you can setup the members of IEnumerable<T>
to achieve the same result. In the case of IEnumerable<T>
all you have to do is set up GetEnumerator()
to return an Enumerator<T>
that enumerates over two values.
In this situation I usually create that Enumerator<T>
by creating a new List with a couple of items and calling GetEnumerator()
on it:
mockUsers.Setup(m => m.GetEnumerator()).Returns(new List<Users> {
new User(),
new User()
}.GetEnumerator());
Now, of course this effectively tests the extension method Count()
in addition to whatever you are trying to achieve with your test, while that is a pretty low risk when the extension method is a part of .NET, it is something to keep in mind if you are using and authoring extension methods of your own.
Upvotes: 1