While preparing to record a Microsoft Virtual Academy course
on Build for 2015 I ran into the situation where I needed to test a controller
that relied on a DbContext. I thought
this would be a great opportunity to document what it takes to do so. For the purpose of this blog post we will be
using a publically available project PartsUnlimited located here https://github.com/Microsoft/PartsUnlimited. We will be working out of the aspnet45
branch.
With the project loaded in Visual Studio 2013 Update 4 let’s
begin by adding a test project.
1.
Right click on src folder and select Add
>> New Project...
2.
Select Test and select Unit Test Project.
3.
Change the .NET Framework to 4.5.1.
4.
Name the project "PartsUnlimitedWebsite.Test" and
click OK.
With the test project added we need to make a reference to
the PartsUnlimitedWebsite project.
1.
Right click on the PartsUnlimitedWebsite.Test
project and select Add >> Reference…
2.
Select the Solution section on the left hand
side.
3.
Check the box next to PartsUnlimitedWebsite and
click OK.
We also need to add a reference to EntityFramework and Microsoft ASP.NET MVC.
1.
Right click on the solution and select Manage
NuGet Packages for Solution…
2.
Select EntityFramework then click the Manage
button.
3.
Check the box for PartsUnlimitedWebsite.Test and
click OK.
4. Select Microsoft ASP.NET MVC then click the Manage button.
5. Check the box for PartsUnlimitedWebsite.Test and click OK.
6.
Now click Close on the PartsUnlimited.sln Manage
NuGet Packages dialog.
Now we need to produce fakes for both the
PartsUnlimtedWebsite and EntityFramework assemblies.
1.
Expand the References node under the PartsUnlimitedWebsite.Test
project.
2.
Right click on EntityFramework and select Add
Fakes Assembly.
3.
Right click on PartsUnlimitedWebsite and select
Add Fakes Assembly.
Following the steps above will create a new Fakes folder in
your project with two files EntityFramework.fakes and PartsUnlimited.fakes.
The next section is optional. If you
build the solution right now you should build with no errors but you will have two
warnings. The warnings will state that some fakes could not be generated. The following steps will remove those
warnings.
1.
Double click EntityFramework.fakes.
2.
Replace the file’s contents with the following:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
<Assembly Name="EntityFramework" Version="6.0.0.0"/>
<StubGeneration>
<Clear/>
<Add TypeName="IDbSet"/>
</StubGeneration>
<ShimGeneration>
<Clear/>
</ShimGeneration>
</Fakes>
3.
Double click PartsUnlimited.fakes.
4.
Replace the file’s contents with the following:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/" Diagnostic="true">
<Assembly Name="PartsUnlimited"/>
<StubGeneration>
<Clear />
<Add FullName="PartsUnlimited.Models.IPartsUnlimitedContext"/>
</StubGeneration>
<ShimGeneration>
<Clear />
</ShimGeneration>
</Fakes>
Making these changes are instructing the Fakes engine to
only generate stubs for two types instead of the entire assembly. This will get rid of the two warnings and
reduce our build time.
With the stubs generated we can now turn our attention to
writing the test. We are going to create
a test for the Index method of the HomeController class. This method uses the DbContext class to try
and retrieve products. Our goal is to be
able to call this method in a test without requiring access to a database. To
do so we are going to fake the IPartsUnlimitedContext interface required by the
HomeController constructor.
1.
Right click the UnitTest1.cs file and select
Rename.
2.
Change the name to "HomeControllerTests" and press
Enter.
3.
Click Yes on the dialog asking if you would like
to rename all references.
4.
Rename the TestMethod1 method to "HomeContoller_Index".
5.
Finally replace the using statements at the top
of the file with the following:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PartsUnlimited.Controllers;
using PartsUnlimited.Models;
using PartsUnlimited.Models.Fakes;
using System.Collections.Generic;
using System.Data.Entity.Fakes;
using System.Linq;
With the file, class and method renamed we can start writing
our test. I use the triple A method of test writing so let’s add the Arrange,
Act and Assert comments to frame our test.
public void HomeContoller_Index()
{
//
Arrange
// Act
//
Assert
}
Most of our work will take place in the arrange section
where we will fake our IPartsUnlimitedContext and IDbSet interfaces. Because we
are going to be accessing the products we are going to create a sample product
we can use in our test. While building
this test I realized that the OrderDetails collection of the product is accessed.
When using Fakes you only have to fake the portions of your stubs that are accessed
during the execution of your test.
Therefore, I only set the OrderDetails property of my product object.
var product = new Product
{
OrderDetails = new List<OrderDetail>()
};
A DbSet in EntityFramework is a collection. So we are going to
create a simple List to back our fake DBSet.
The only entry in this list will be the product we just created,
however, you could add as many products as you need for your testing.
//
Create a list to hold the products to be returned by the model context
var productList = new List<Product>
{ product };
Now it is time to fake the IPartsUnlimitedContext interface.
This is the first time we will actually be using the generated types
StubIPartsUnlimitedContext and StubIDbSet.
Fakes will create classes for us that we can use to code the desired behavior
during our test. As I mentioned before
we only have to implement the portion of our stubs that are accessed during our
test. Because we are only going to be working with the products table that is
the only DbSet we are going to implement.
LINQ is used to query the DbSet which accesses two properties
the Expression and the Provider. Luckily
we can use our list we created earlier to provide the expression and provider
of our fake.
var context = new StubIPartsUnlimitedContext
{
ProductsGet = () => new StubIDbSet<Product>()
{
ExpressionGet = () =>
productList.AsQueryable().Expression,
ProviderGet = () =>
productList.AsQueryable().Provider
}
};
Now comes the easy part.
Simply pass our fake IPartsUnlimitedContext to the constructor of our
HomeController and call the Index method.
var target = new HomeController(context);
//
Act
var actual = target.Index();
Finally you can now assert that some state is true.
//
Assert
Assert.IsNotNull(actual);
Now you can run this test and even step through the code to
see how the fakes allow you to test this code with no database. Fakes is an incredible isolation framework
for writing unit test. You can learn
more about fakes here.