ASP.NET N-Layered Applications - Making your Projects Unit Testable (Part 3)

Note: this is part three in a series of ten. If you rather read this entire series off-line, you can buy the full series as a convenient PDF document that comes with the full source. Besides the convenience, buying the PDF will also make you feel good as it shows your appreciation for the articles and helps me pay the bills for my server and hosting so I can keep running imar.spaanjaars.com and continue to provide you with great content. For more details, check out this post that shows you how you can buy the entire series right now.

This is Part 3 in a series of 10 that show you how to build N-Layered applications using ASP.NET 4.5 and Entity Framework 5 Code First. In this part you’ll see how to make your solution unit testable. In addition, you’ll see how to setup a project for Integration tests which work similar to unit tests but that target the database directly.

If you haven’t read the earlier parts in this series yet, you’re encouraged to do so first. The following list has links to all articles in this series:

Note: as mentioned in Part 1, this article (and the previous) will contain a lot of hands-on, step by step instructions that show you how to setup a solution like the Spaanjaars.ContactManager application. You can use these instructions pretty much as-is for your own applications. The remaining parts in the series analyze the working code for the Spaanjaars.ContactManager application. I’ll show a lot of the code in detail, and explain how it works, but you won’t find detailed step by step instructions on how to add the code and files to the various projects.

Making Your Projects Unit Testable

To ensure a better quality of your software, it’s highly recommended to add unit tests to your projects. This way, you can test your code during development, minimizing the chance of introducing issues and finding and fixing them before they ever make it into production code. This article does not explain the need for unit testing in detail nor does it explain how to write good unit tests. For a good introduction into unit testing, check out the following references:

This article does, however, show you how to differentiate your types of tests (using different Test Projects in Visual Studio) and how to configure the solution so that the test projects can see the relevant assemblies in the solution. In many previous solutions I built, I use more than one test project, described in the following table:

Type Suggested name suffix Description

Unit

Tests.Unit

This project contains all the unit tests that don’t have a dependency on a database or the UI. In the sample application, it contains tests for model entity properties, model validation, and more.
For applications with a larger model you could further separate the unit tests into separate Visual Studio projects, each named after the area of the application they are targeting.

Integration

Tests.Integration

This test project contains integration tests that have dependencies to other components of the system, such as a database. In the sample project you find tests that use the Repositories.EF project to interact with a SQL Server database directly.
For applications with a larger model you could further separate the integration tests into separate Visual Studio projects, each named after the area of the application they are targeting.

Frontend

Tests.Frontend.Mvc

This test project contains tests that target the presentation layer. It could contain Coded UI tests and tests targeting the ASP.NET MVC controller framework. In the sample application you only find unit tests for MVC controllers but you could easily add more tests to this project.

Frontend

Tests.Frontend.Wcf

This test project contains tests for the various service methods inside the WCF service project.


In the next steps you’ll see how to add these four Test Projects to the solution. I am showing you how to add all four; one for each frontend application added to the solution in the previous article. If, however, your solution has fewer frontend implementations, only add the test projects for your particular frontend(s).

In order to better organize my solution, I prefer to group all my test projects in a Solution Folder called Tests. This is not required, but it makes it easier to get access to all your test projects at once, or hide them when you’re working on the actual code for the project.

  1. To add a Solution Folder for the tests, right-click the Solution in the Solution Explorer and choose Add | New Solution Folder. Name the folder Tests.
  2. Right-click this new Tests folder and choose Add | New Project. Select your preferred programming language and then, from the Test category, select Unit Test Project. Name the project Spaanjaars.ContactManager45.Tests.Unit and change the Location for the project to C:\Projects\Spaanjaars.ContactManager45\Main\Tests. By storing your test projects in the Tests folder you separate them from the actual implementation projects that are stored in the Applications folder. In the Target Framework drop-down list make sure .NET Framework 4.5 is selected.

    This project is going to contain the core unit tests for the Contact Manager application.

The Add New Project Dialog
Figure 3-1 The Add New Project Dialog (click to enlarge)

  1. Click OK to add the project to the solution.
  2. Add another Unit Test Project to the Tests Solution Folder, name it Spaanjaars.ContactManager45.Tests.Integration and make sure it’s saved in the folder C:\Projects\Spaanjaars.ContactManager45\Main\Tests as well. This project will contain tests that are going to access the database directly.
  3. Add another Unit Test Project to the Tests Solution Folder, name it Spaanjaars.ContactManager45.Tests.Frontend.Mvc and make sure it’s saved in the folder C:\Projects\Spaanjaars.ContactManager45\Main\Tests. This project will contain tests that target the ASP.NET MVC 4 project.
  4. Add another Unit Test Project to the Tests Solution Folder, name it Spaanjaars.ContactManager45.Tests.Frontend.Wcf and make sure it’s saved in the folder C:\Projects\Spaanjaars.ContactManager45\Main\Tests. This project will contain tests that target the WCF service project.
  5. In these four new test projects, add references to the following projects in your solution:
Project References

Tests.Unit

  • Spaanjaars.ContactManager45.Infrastructure
  • Spaanjaars.ContactManager45.Model

Tests.Integration

  • Spaanjaars.ContactManager45.Infrastructure
  • Spaanjaars.ContactManager45.Model
  • Spaanjaars.ContactManager45.Respositories.EF

Tests.Frontend.Mvc

  • Spaanjaars.ContactManager45.Infrastructure
  • Spaanjaars.ContactManager45.Model
  • Spaanjaars.ContactManager45.Web.Mvc
  • System.Web.Mvc (found in %programfiles(x86)%\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies by default)

Tests.Frontend.Wcf

  • Spaanjaars.ContactManager45.Infrastructure
  • Spaanjaars.ContactManager45.Model
  • Spaanjaars.ContactManager45.Web.Wcf


Your Solution Explorer should now look like this:

The Solution Explorer with the new Unit Test Projects
Figure 3-2 The Solution Explorer with the new Unit Test Projects

On disk your Solution should look like this in File Explorer:

File Explorer Showing the new Unit Test Projects
Figure 3-3 File Explorer Showing the new Unit Test Projects (click to enlarge)

  1. Finalize the configuration of your projects. For example, you can delete the existing default Unit Test files (called UnitTest1.cs inside each project). You can also add additional packages such as FluentAssertions (for which you’ll find instructions later in this article) or references to external libraries.
  2. Finally, do a full rebuild of the entire solution to ensure everything is set up correctly and resolve any compilation errors you may have.

In the next section you’ll see how to add a simple test to three of the four Unit Test projects in order to ensure your projects, including the Infrastructure, Model and Repositories.EF projects as well as the Unit Test projects itself, have been set up correctly. In order for this to work, I’ll also add a few classes to the core projects with some temporary implementation. That code will be expanded or replaced in later articles.

  1. In the Model project, add a new public class called Person and give it an automatically implemented property of type int called Id. You should end up with code like this:
namespace Spaanjaars.ContactManager45.Model
{
  public class Person
  {
    public int Id { get; set; }
  }
}
  1. In the project Spaanjaars.ContactManager45.Tests.Unit add a new Unit Test file called PersonTests.cs and modify the code as follows.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Spaanjaars.ContactManager45.Model;

namespace Spaanjaars.ContactManager45.Tests.Unit
{
  [TestClass]
  public class PersonTests 
  {
    [TestMethod]
    public void NewPersonHasEmptyId()
    {
      var person = new Person();
      Assert.AreEqual(0, person.Id);
    }
  }
}
  1. Run all the tests (there’s only one at the moment) in the solution by pressing Ctrl+R, followed by an A (Ctrl+R, A) or by choosing Tests | Run | All Tests from the main menu. Then check the Test Explorer (which you can open using Test | Windows | Test Explorer). You should see that the test has passed:

    Test Explorer Showing Success
    Figure 3-4 Test Explorer Showing Success

    Note: You would normally write a test that fails first, to avoid ending up with false positives. You would then implement some code to make the test pass. In this case, the test is so simple (and not really meant to test the functionality of the Person class, but rather the setup of the test project itself) that I decided to write a "green test" directly.

  1. Next, add a new public class called PeopleRepository.cs to the Spaanjaars.ContactManager45.Repositories.EF project. Don’t forget to add public in front of the class or it’ll default to internal. There’s no need to add any code to the class for now.
  2. In the project Spaanjaars.ContactManager45.Tests.Integration, add a new Unit Test file, call it PeopleRepositoryTests.cs and modify the code as follows:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Spaanjaars.ContactManager45.Repositories.EF;

namespace Spaanjaars.ContactManager45.Tests.Integration
{
  [TestClass]
  public class PeopleRepositoryTests 
  {
    [TestMethod]
    public void CanInstantiatePeopleRepository()
    {
      var peopleRepository = new PeopleRepository();
      Assert.IsNotNull(peopleRepository);
    }
  }
}

Note: this is a pretty useless test. You should have other tests that implicitly check whether you can instantiate a new PeopleRepository class and then work with that instance. However, for now this test is useful to make sure that the Integration project has the correct references to the Repositories.EF project.

  1. Press Ctrl+R, A again. Both tests should pass.
  2. In the test project Spaanjaars.ContactManager45.Tests.Frontend.Mvc add a new Unit Test file called HomeControllerTests.cs and modify its code as follows:
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Spaanjaars.ContactManager45.Web.Mvc.Controllers; 

namespace Spaanjaars.ContactManager45.Tests.Frontend.Mvc
{
 [TestClass]
 public class HomeControllerTests 
  {
    [TestMethod]
    public void IndexHasNoModel()
    {
      var controller = new HomeController();
      var result = controller.Index() as ViewResult;
      Assert.AreEqual(null, result.Model);
    }
  }
}
  1. Press Ctrl+R, A once more. All three tests should now pass, as shown in Figure 3-5:

Test Explorer Showing Green Tests
Figure 3-5 Test Explorer Showing Green Tests

For now, these tests are really simple and only serve to verify the setup and relationships between the various projects. I skipped the tests for the WCF service as the current project doesn’t have any code files and adding a WCF service requires more background which I’ll cover in Part 8 of this series.

I’ll be adding more tests to the test projects as I progress through this article series. I won’t show you the code for all of the tests though; I’ll only highlight some of the more important ones. You’re encouraged to check out the source code that comes with this article series to see all the tests.

Using Fluent Assertions

There are a number of open source frameworks available that make unit tests easier to read and more obvious. One of those frameworks is FluentAssertions created by Dennis Doomen. This framework allows you write the assertions for your test in a fluent way. E.g. rather than writing something like this:

Assert.AreEqual(3, id);

You can now write something like this:

id.Should().Be(3);

Although you may need to get used to this new syntax at first, I generally find it more intuitive to read. It’s very useful to quickly see what your tests do but more importantly, non-technical people involved in a project can now more or less read unit tests too. This enables them to help you write the proper tests.

Under the hood, the various FluentAssertions methods check your values and raise exceptions when a condition is not met. This exception is eventually caught by MS test or another test framework you may be using.

In order to change your test projects to use FluentAssertions, follow these steps:

  1. Inside Visual Studio, open up the Package Manager Console by choosing Tools | Library Package Manager | Package Manager Console.
  2. Select your Integration test project from the Default project drop-down (called Spaanjaars.ContactManager45.Tests.Integration if you’re following along with the walkthroughs in this article series).
  3. In the Package Manager Console type Install-Package FluentAssertions and hit enter.
  4. Repeat step 2 and 3, three more times, but now add FluentAssertions to the Unit tests project, the Frontend.Mvc project and the Frontend.Wcf project.
  5. Open up the PersonTests class in the Unit Tests project and at the top of the file add the following using statement:
    using FluentAssertions;
  6. Change the last line of the NewPersonHasEmptyId test to the following:
    person.Id.Should().Be(0); 
    With this change, the code almost reads like English; the ID of the new person created in code should be zero.
  7. Next, run your test(s) to see if everything still works. If they pass (and they should), you should see a nice green checkmark in the Unit Test Explorer:

Test Explorer Showing Green Tests
Figure 3-6 Test Explorer Showing Green Tests

You can find out more about FluentAssertions on the project’s site at CodePlex.com: http://fluentassertions.codeplex.com/.

Stuff I Like to Do

Here’s a quick list of things I like to do when it comes to unit testing:

Summary

In this article you saw how to add a number of different unit test projects to your solution and how to set them up. While you could technically add all your tests to a single unit test project, I prefer to create separate projects for different types of tests. This allows me to decide when to run which tests. For example, because Integration tests generally run slower than plain unit tests (because they access databases and other slow resources), I could decide to run my unit tests after each build while the integration tests are only run when I check in code into TFS.

At this point, each test project contains a simple unit test that is used purely to check if the test projects were setup correctly. In later parts in this article series you’ll see how to add more useful tests to the different test projects.

With all the “plumbing” done, the next step is to start building the model. In the next part in this article series you’ll see how to set up the model using POCO – Plain Old CLR Objects - classes. That model is then used in Part 5 that describes how to build a repository that targets Entity Framework 5 Code First to interact with the database. In the articles following Part 5 I’ll show you how to use the model and the repositories to build various frontend applications.

Downloads


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article .


Consider making a donation
Please consider making a donation using PayPal. Your donation helps me to pay the bills so I can keep running Imar.Spaanjaars.Com, providing fresh content as often as possible.



Feedback by Other Visitors of Imar.Spaanjaars.Com

On Monday, August 04, 2014 5:14:14 PM Robin A. Simmons said:
Hello Imar,
I am creating your N-Layered Application using VB instead of C#.  I am at Part 3 and everything so far is exactly as the C# version with no errors.  I'm implementing the MVC Unit Test.  There is a line of code as follows: using Spaanjaars.ContactManager45.Web.Mvc.Controllers; In my VB version, the Controllers portion of the line is not in the namespace and does not appear in Intellisense.  The folder is there in the Project.

I've checked all my work and it does not appear I've made any mistakes, but I'm sure it's a simple solution.  Any assistance would be appreciated.

Regards,
Robin
On Monday, August 04, 2014 5:47:36 PM Imar Spaanjaars said:
Hi Robin,

That namespace is created by VS when you create new types in the Controller folder for C#(it automatically appends the folder's name to the namespace). In VB.NET you have a root namespace (set at the project level) and everything by default falls in that namespace. You should be able to fix it by adding an Imports statement (instead of using in C#) for the Spaanjaars.ContactManager45.Web.Mvc namespace to your Controller files.

However, personally I prefer to add the namespace around the controller. E.g.:

Namespace Spaanjaars.ContactManager45.Web.Mvc.Controllers

Public Class PersonController
    Inherits......
  .....

End Namespace

Hope this helps,

Imar
On Monday, August 04, 2014 9:28:18 PM Robin A. Simmons said:
Thank you for getting back with me so quickly.

I added the "Imports Spaanjaars.ContactManager45.Web.Mvc" line to the HomeControllerTest.vb file and it fixed the problem.  However, when I add "Namespace Spaanjaars.ContactManager45.Web.Mvc.Controllers" to the HomeController.vb file, I then have to change the Imports line to read "Imports Spaanjaars.ContactManager45.Web.Mvc.Spaanjaars.ContactManager45.Web.Mvc.Controllers".  It repeats the Namespace.

I've tried numerous things to fix this with no joy.  I would like the Imports line to read "Imports Spaanjaars.ContactManager45.Web.Mvc.Controllers"

I'm pretty new at this, so I understand if it's a larger fix that I'll have to dig deeper to understand.

Thank you for your help.
Robin
On Monday, August 04, 2014 9:43:01 PM Imar Spaanjaars said:
That's caused by the fact that VB.NET uses a default namespace. This means you should put your controllers in the namespace Controllers (instead of Spaanjaars.ContactManager45.Web.Mvc.Controllers) as VB adds the rest automatically.

Cheers,

Imar
On Saturday, September 19, 2015 3:52:18 PM William Reed said:
I am using visual studio 2013....

I am up to the point where i need to reference system.web.mvc inside the unit test project for frontend.mvc When I try to run my tests on that step I Get this...

Error 2 Assembly 'xxxxx.ContactManager45.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' uses 'System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' which has a higher version than referenced assembly 'System.Web.Mvc, Version=4.0.0.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35' xxxxxxx.dll xxxx.ContactManager45.Tests.Frontend.Mvc

Now I have tried referencing different mvc dll's but I keep getting this error.  Now I assume that the web one is newer but I don't know how to get that dll, I have tried a bunch of different type of referencing.  I will do some more googling and what not to see if I can figure it out.  Great article btw!  This is just something I am sure i messed up.
On Saturday, September 19, 2015 3:58:31 PM William Reed said:
of course after I post it works....

I re-referenced the path to the dll from the MVC frontend project....bingo...

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the QuickDocId of the document.

For more information about the Talk Back feature, check out this news item.