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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With