Last week I attempted to mimic this article from Microsoft: Testing and Mocking Framework. I ran into a problem involving some code that was in the article last week, but has since been removed (I’m assuming that Microsoft changed something in their EF6 framework or .Net and didn’t update the article before I tested it). Anyway, I have now duplicated the unit tests that Microsoft demonstrates with my own table.
Two “gotchas” were discovered while attempting to make this work. First, I had to pass the context into my object (instead of just using a “using” statement with the context I needed. The reason for this is so the unit test can pass in a mock context. The second “gotcha” was that the DataSet object inside the EF code needs to be virtual. This was more difficult due to the fact that the code itself was auto-generated (because I used the EF designer to create it). Fortunately, there is a reference in the article that states that “virtual” can be added to the T4 template used by EF (inside the .Context.tt file). I added the virtual keyword and my unit test worked like a charm.
The Details
So I wrote a console application and created one table. The table was generated in MS SQL server. I named the table “account” and put a couple of fields in it (the primary key field was set up as an “identity” field so it will generate a unique key upon insert). Here’s the ERD:
Don’t laugh, I like to start off with really simple stuff. Things seem to get complicated on their own.
Make sure you use NuGet to download the version 6.0.1 (or later) version of Entity Framework. I just opened the NuGet window (see “Tools -> Library Package Manager -> Manager NuGet Packages for Solution…”), then I typed “Entity Framework” in the “Search Online” search box.
My console application main program looks like this:
using System; namespace DatabaseTestConsole { class Program { static void Main(string[] args) { UserRights rights = new UserRights(new DatabaseContext()); string test = rights.LookupPassword("test"); Console.WriteLine(test); } } }
My entity container name is called “DatabaseContext”, I created the container using the project right-click, then “add” -> “new item”, then selecting “ADO .Net Entity Data Model”. I added a connection and dragged my table to the EF Model Diagram.
Then I created a new class called “UserRights” (right-click on project, “add” -> “class”). This is the content of the UserRights.cs file:
using System.Linq; namespace DatabaseTestConsole { public class UserRights { private DatabaseContext _context; public UserRights(DatabaseContext context) { _context = context; } public string LookupPassword(string userName) { var query = (from a in _context.accounts where a.username == userName select a).FirstOrDefault(); return query.pass; } } }
I manually added some data into my table and tested my program, just to make sure it worked. Then I added a unit test source file (I named it “UnitTests.cs”), using the same “add -> class” that I used to create the UserRights.cs file above. Then I added in two references and usings for unit testing and moq. Here’s the entire source code for the test:
using System.Collections.Generic; using System.Data.Entity; using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; namespace DatabaseTestConsole { [TestClass] public class UnitTests { [TestMethod] public void TestQuery() { var data = new List<account> { new account { username = "test",pass="testpass1" }, new account { username = "ZZZ",pass="testpass2" }, new account { username = "AAA",pass="testpass3" }, }.AsQueryable(); var mockSet = new Mock<DbSet<account>>(); mockSet.As<IQueryable<account>>().Setup(m => m.Provider) .Returns(data.Provider); mockSet.As<IQueryable<account>>().Setup(m => m.Expression) .Returns(data.Expression); mockSet.As<IQueryable<account>>().Setup(m => m.ElementType) .Returns(data.ElementType); mockSet.As<IQueryable<account>>().Setup(m => m.GetEnumerator()) .Returns(data.GetEnumerator()); var mockContext = new Mock<DatabaseContext>(); mockContext.Setup(c => c.accounts).Returns(mockSet.Object); UserRights rights = new UserRights(mockContext.Object); Assert.AreEqual("testpass1", rights.LookupPassword("test"), "password for account test is incorrect"); Assert.AreEqual("testpass2", rights.LookupPassword("ZZZ"), "password for account ZZZ is incorrect"); Assert.AreEqual("testpass3", rights.LookupPassword("AAA"), "password for account AAA is incorrect"); } } }
As you can see from the unit test (called “TestQuery”) above, three rows of data are inserted into the mocked up account table. Then the mock context is setup and the UserRights object is executed to see if the correct result is read from the mock data. If you want to test this for yourself, go ahead and copy the code segments from this article and put it into your own project. Unit testing methods that perform a lot of database operations will be easy using this technique and I also plan to use this for end-to-end integration testing.
Where to Get the Code
I have posted the code on GitHub, you can click https://github.com/fdecaire/MockingEF6 to download the code and try it yourself.
I'm trying to implement the same unit test from the same Microsoft article. I run into issues when I try to use an include. The source is null. When I debug the context, I can see the collection, and the objects inside of. If I look again a second later, they're all gone. Have you encountered anything like this yourself?
I apologize for taking so long to respond to your question. I only blog on the weekends. I have not ran into any issues similar to what you describe, but I have only written this one example and I might have just been lucky. I'll be using this technique for our production system soon, so I'm betting that I'll run into every quirk EF-6 has in it. I am currently developing with Visual Studio 2012 Update 3 (version 4.5.50709 in the about box). EF-6 seems to be in it's bleeding edge phase and Microsoft is changing their information and putting out patches. I'm going to create another table and setup a different unit test to see if I run into any issues.
Don't forget to search in your .Context.tt script file for the DBSet definition and add "virtual":
public string DbSet(EntitySet entitySet)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0} virtual DbSet<{1}> {2} {{ get; set; }}",
Accessibility.ForReadOnlyProperty(entitySet),
_typeMapper.GetTypeName(entitySet.ElementType),
_code.Escape(entitySet));
}
Hi Frank
Excellent article.
1) Did you have any success regarding the ".Include()" scenario as you commented on?
2) I'm hoping all the Moq code above can be achieved in MS FAKES framework? I've never used Moq
Cheers
Kyle
I have been using the fakes framework that Microsoft demonstrates. It seems to work smoother than the moq. There are a few things that can't be easily faked or mocked. The SqlFunctions.StringConvert() is notorious as well as the "removerange()" method. If you read through my newer blog posts, you'll discover that I'm researching NHibernate in hopes to get around a lot of these issues that EF has.
Hi Frank
Great Article. I have been trying to unit test using Moq by following your example and also this example (http://msdn.microsoft.com/en-gb/data/dn314429.aspx) Do you have an example of a delete operation unit test. I have been trying to work this out but I cannot find any examples. I have a question open on Stackoverflow (http://stackoverflow.com/questions/22882472/how-to-use-moq-to-unit-test-a-delete-operation-in-entity-framework-6). Any help would be appreciated. I have trying to figure this out for days, Even though the test passes the entity is not removed from the list. I think it is because it is an IQueryable, but don't know how to fix it
Awesome! it works like a charm…Thanks!
Any luck with the delete? It works perfectly if I set my context from test and perform read operations in my code. But when I try to delete a record, the result view which showed 2 records initially, now says "Enumeration yielded no results". If I do a query, it still returns 2 records instead of 1.
This is what I have:
var myTable = Context.MyTableList.Where(x => x.ListId == myListId).FirstOrDefault();
if (myTable != null)
{
Context.MyTableList.Remove(myTable);
Context.SaveChanges();
}
At this point the resultview says "Enumeration yielded no results"
If I do a query right after my delete:
var myTable = Context.MyTableList.Where(x => x.ListId == myListId).FirstOrDefault();
It still shows me 2 records instead if 1.
Is it due to mock that doesn't let you modify the content during testing?
I just setup a project to simulate what you are talking about and you're right. I cannot remove records from the mocked table. I've only used the Moq object a few times in production. After I found out about doubles, I setup a double object for my context and it works much smoother (and the test code is really small). In the test double, you actually provide a method to remove records. I have hundreds of tests using the doubles method. See this Microsoft article for more info: http://msdn.microsoft.com/en-us/data/dn314429.aspx#options
I am getting a compilation error on accounts
You're in luck! I still have the code. It's on Github: https://github.com/fdecaire/MockingEF6
I'll amend this article to include the Git reference.