DotNetNukeŽ Unit Testing : Easy to use /  Easily Create Sample Data To Simulate a Linq To SQL Database

Frequently programmers are challenged with implementing complex business rules. While it is possible to implement them in a beautiful well designed program, the real challenge comes when the business rules constantly change and the code becomes "hacky". Worse yet, new changes can cause previously working code to break. The answer to this dilemma is Unit Testing. 

The challenge of Unit Testing is that a proper test of the code usually involves data from the database. This sample program demonstrates how to use the "Repository Pattern" to easily create sample data for Unit Testing.

When faced with implementing complex business rules, Unit testing can be helpful as it allows you to easily compartmentalize the various rules and allow you to easily see if implementing one rule breaks another.

This is not traditional Unit Testing where it is suggested that you write a Unit Test first then implement the code to "pass the test". That sort of development is considered by many to be time consuming (it is) and therefore costly. Proponents argue that it saves you time when implementing changes (it does). This article assumes that you have decided that Unit Testing is too costly and expensive, but you desire to use the benefits of Unit Testing for a few key components.

One of the major challenges to properly Unit Testing a component is that you usually have a complex database structure that has data from various tables that must be considered in the test. A Unit Test passes a pre-defined input to a component and checks it against an expected output. If the output is as expected the test "passes". Creating this pre-defined input when the component being tested expects the input to be "The Database And All The Tables In The Database" is difficult. Various solutions exist, and this article proposes an easy solution.

This Article Covers:

Please note that this article purposefully avoids discussion of "proper separation of dependencies". In this example your Unit tests will reference the Linq to SQL class and directly use it's classes that represent the tables in the database. Purists would argue that this means the test result is not completely isolated because code in the Linq to SQL class itself would run and alter the results. In our example we actually desire this sort of thing because when the code ran in production this is what would actually happen.

This article is designed for the developer who is not a Unit Testing purist, just a person who wants to use the benefits of Unit Testing.

Note, you will need Visual Studio 2008 Professional or above to use it's Unit Testing framework.

The "Compute Best Customer" Module

Lets start by demonstrating the completed module that implements the "Repository Pattern". Install the CustomerRating_01.00.00_Install.zip file (at the end of the article) into your DotNetNuke website and place an instance on a page in your DotNetNuke website.

When you install the module, it creates a Customer table and inserts three Customers.

It also creates an Orders table and creates some sample orders for the Customers.

When you run the module and you select Database as the Data Source, and you select a Customer for the Customer 1 and Customer 2 dropdowns, and click the Compute Best Customer button, it will "determine the best customer" by calling a ComputeBestCustomer class.

The class implements the following "Business Rules":

Looking at the data shown earlier, choose two customers, determine what the result should be, and click the button and see the result.

You would probably not need Unit Testing for three simple rules, but, what if there were 20 rules, or a hundred? This is the point where you may be willing to pay the price of code and time to implement Unit Testing.

This is the code for the ComputeBestCustomer class:

        #region ComputeBestCustomer
        public CustomerRating_Customer ComputeBestCustomer(int CustomerOneID, int CustomerTwoID)
        {
            CustomerRating_Customer BestCustomer = null;

            // Get Customers
            CustomerRating_Customer CustomerOne = GetCustomer(CustomerOneID);
            CustomerRating_Customer CustomerTwo = GetCustomer(CustomerTwoID);

            // Get Customer Orders
            var CustomerOneOrders = GetOrders(CustomerOne.CustomerID);
            var CustomerTwoOrders = GetOrders(CustomerTwo.CustomerID);

            // Get Raw values
            int CustomerOneOrdersCount = CustomerOneOrders.Count();
            int CustomerTwoOrdersCount = CustomerTwoOrders.Count();

            Decimal CustomerOneAverage = CustomerOneOrders.Average(Orders => Orders.Amount);
            Decimal CustomerTwoAverage = CustomerTwoOrders.Average(Orders => Orders.Amount);

            Decimal CustomerOneTotal = CustomerOneOrders.Sum(Orders => Orders.Amount);
            Decimal CustomerTwoTotal = CustomerTwoOrders.Sum(Orders => Orders.Amount);

            // Compute best customer

            // Rule #1: If a customer has more than 10 orders than the other customer
            // they are automatically the best customer
            if (CustomerOneOrdersCount > 10 || CustomerTwoOrdersCount > 10)
            {
                if (CustomerOneOrdersCount > 10 & CustomerTwoOrdersCount <= 10)
                {
                    return CustomerOne;
                }

                if (CustomerTwoOrdersCount > 10 & CustomerOneOrdersCount <= 10)
                {
                    return CustomerTwo;
                }
            }

            // Rule #2: If a customer has an average order total that is 10 more than the other customer
            // they are automatically the best customer
            decimal dDifference = Convert.ToDecimal(10);

            if (((CustomerOneAverage - CustomerTwoAverage) > dDifference)
                || ((CustomerTwoAverage - CustomerOneAverage) > dDifference))
            {
                if ((CustomerOneAverage - CustomerTwoAverage) > dDifference)
                {
                    return CustomerOne;
                }

                if ((CustomerTwoAverage - CustomerOneAverage) > dDifference)
                {
                    return CustomerTwo;
                }
            }

            // Rule #3: The customer with the highest order total is the best customer
            if (CustomerOneTotal > CustomerTwoTotal)
            {
                return CustomerOne;
            }

            if (CustomerTwoTotal > CustomerOneTotal)
            {
                return CustomerTwo;
            }

            return BestCustomer;
        }
        #endregion
Note that we are able to use Linq code to transform and query the data. For example, the code to get the average customer order is using Linq. This code will work whether we are connected to an actual database or we are connected to sample "mocked up" data.

It is important to note that this class is actually connecting to the database.  The class looks like we would normally code it, except we don't have the usual code to directly insatiate the Linq to SQL Datacontext. We have code that calls methods such as GetCustomer.

        #region GetCustomer
        public CustomerRating_Customer GetCustomer(int CustomerID)
        {
            var result = CustomerRepository.GetCustomer(CustomerID);

            return result;
        }
        #endregion
If we look at GetCustomer we see that it is calling a method called GetCustomer on a object called CustomerRepository.
    public class CustomerRatingBusiness
    {
        ICustomerRatingRepository CustomerRepository;

        // This is the default constructor
        // Instantiating this class without passing it a repository will 
        // cause it to connect to the repository that uses the Linq to SQL class
        public CustomerRatingBusiness()
            : this(new CustomerRatingRepository()) { }

        // This construct allows you to pass any repository you want
        // Here you can pass a repository of sample testing data
        public CustomerRatingBusiness(ICustomerRatingRepository repository)
        {
            CustomerRepository = repository;
        }
We then see that CustomerRepository is a class that is created one way if the default constructor is used to create the class, and another way if a repository object is passed to the class (if a repository object is pass to the class, the class will use that repository object).

If we right-click on the CustomRatingRepository() class that is created by the default constructor...

    public class CustomerRatingRepository : ICustomerRatingRepository
    {
        #region GetCustomers
        public IQueryable<CustomerRating_Customer> GetCustomers()
        {
            CustomerRatingDALDataContext db = new CustomerRatingDALDataContext();

            var result = from Customer in db.CustomerRating_Customers
                         select Customer;

            return result;
        } 
        #endregion

        #region GetOrders
        public IQueryable<CustomerRating_Order> GetOrders(int CustomerID)
        {
            CustomerRatingDALDataContext db = new CustomerRatingDALDataContext();

            var result = from Orders in db.CustomerRating_Orders
                         where Orders.CustomerID == CustomerID
                         select Orders;

            return result;
        } 
        #endregion

        #region GetCustomer
        public CustomerRating_Customer GetCustomer(int CustomerID)
        {
            CustomerRatingDALDataContext db = new CustomerRatingDALDataContext();

            var result = (from Customer in db.CustomerRating_Customers
                          where Customer.CustomerID == CustomerID
                          select Customer).FirstOrDefault();

            return result;
        } 
        #endregion
    }
It navigates to a class that simply connects to the Linq to SQL Datacontext and returns three methods:

It is important to note that the GetCustomers and GetOrders methods return IQueryable. This allows us to use Linq code to further refine and transform the data in our code (note that using IQueryable is where some purists will argue that your repository is not "pure". However, "The Man In The Red Shirt" (Scott Guthrie) used it in the MVC book so it is good enough for me).

The Repository Pattern

We have already demonstrated most of the "Repository Pattern". The only thing missing is the explanation of how an Interface is used to allow us to easily swap out another data source (and not have the code complain).

Remember, our goal is to create a data structure that allows us to use "real" data that comes from our normal Linq to SQL class when running the code in production, but, allow us to also create sample data that we can use for Unit Tests.

Simply put, this is what we will do:

We will now create the Repository and implement it using Linq to SQL (note the complete source code is available in the CustomerRatingProjectSource.zip at the end of this article).

Open your DotNetNuke site in Visual Studio and add a Web Application Project.

Open the Web.config of that project and copy the connection string from your DotNetNuke website. You will create a Linq to SQL class that will use this connection that has "SiteSqLServer" as it's key. This key is what the DotNetNuke site is using, so the code will work when it's assembly is placed in the "bin" directory of the DotNetNuke website.

Create a Linq to SQL class and add the tables from the database. Ensure that the Linq to SQL Datacontext class uses the SiteSqlServer connection.

    public interface ICustomerRatingRepository
    {
        IQueryable<CustomerRating_Customer> GetCustomers();
        IQueryable<CustomerRating_Order> GetOrders(int CustomerID);
        CustomerRating_Customer GetCustomer(int CustomerID);
    }
Create an Interface describing the data access methods you desire. Note the classes returned both directly and through IQueryable are the classes from the Linq to SQL Datacontext class created in the previous step. 

Create a "Repository Class" that implements the Interface and connects to the database using Linq To SQL.

Create a "Business Class" that uses the repository as it's data source. This is the class that contains the "ComputeBestCustomer" method.

You can now instantiate the CustomerRatingBusiness class without a constructor. In that case it will use the CustomerRatingRepository class we just created, that uses Linq to SQL, or pass it a "repository class" that inherits from ICustomerRatingRepository, and it will use that as it's data source instead.

That's it!

It's all easy fun stuff from here on...

Creating Unit Tests

I hope you were able to "hang in" this far. I am the guy who's eyes cross when an article goes into a lot of code and complexity in pursuit of an "ideal vision". You just want to write some Unit Tests! Lets write those tests and let you get back to your life :)

Add a new Test Project to your Solution in Visual Studio.

The test project will be created. After it creates the project, you can delete the ManualTest1.mht file.

Add a reference to the CustomerRating project.

Create a class that inherits from the Interface and implements it's methods, but uses "mocked-up data":

    public class TestDataRepository: ICustomerRatingRepository
    {
        #region GetCustomers
        public IQueryable<CustomerRating_Customer> GetCustomers()
        {
            return CustomerRating_Customers().AsQueryable<CustomerRating_Customer>();
        } 
        #endregion

        #region GetOrders
        public IQueryable<CustomerRating_Order> GetOrders(int CustomerID)
        {
            var result = from Order in CustomerRating_Orders().AsQueryable<CustomerRating_Order>()
                         where Order.CustomerID == CustomerID
                         select Order;

            return result;
        } 
        #endregion

        #region GetCustomer
        public CustomerRating_Customer GetCustomer(int CustomerID)
        {
            var result = (from Customer in CustomerRating_Customers().AsQueryable<CustomerRating_Customer>()
                          where Customer.CustomerID == CustomerID
                          select Customer).FirstOrDefault();

            return result;
        } 
        #endregion

        // Sample Data

        #region CustomerRating_Customers
        private List<CustomerRating_Customer> CustomerRating_Customers()
        {
            List<CustomerRating_Customer> colCustomerRating_Customer = new List<CustomerRating_Customer>();

            colCustomerRating_Customer.Add(new CustomerRating_Customer 
            { CustomerID = 1, CustomerName = "Test One" });

            colCustomerRating_Customer.Add(new CustomerRating_Customer 
            { CustomerID = 2, CustomerName = "Test Two" });

            colCustomerRating_Customer.Add(new CustomerRating_Customer 
            { CustomerID = 3, CustomerName = "Test Three" });

            return colCustomerRating_Customer;
        } 
        #endregion

        #region CustomerRating_Orders
        private List<CustomerRating_Order> CustomerRating_Orders()
        {
            List<CustomerRating_Order> colCustomerRating_Order = new List<CustomerRating_Order>();

            colCustomerRating_Order.Add(new CustomerRating_Order 
            { OrderID = 1, CustomerID = 1, Product = "Widget 1", Amount = Convert.ToDecimal(1.00) });

            colCustomerRating_Order.Add(new CustomerRating_Order 
            { OrderID = 2, CustomerID = 2, Product = "Widget 1", Amount = Convert.ToDecimal(1.00) });

            colCustomerRating_Order.Add(new CustomerRating_Order 
            { OrderID = 3, CustomerID = 2, Product = "Widget 2", Amount = Convert.ToDecimal(2.00) });

            colCustomerRating_Order.Add(new CustomerRating_Order 
            { OrderID = 4, CustomerID = 3, Product = "Widget 3", Amount = Convert.ToDecimal(10.00) });

            return colCustomerRating_Order;
        }
        #endregion
    }
Create a class that contains the Unit Tests: 

using CustomerRating;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestTools.UnitTesting.Web;

namespace MyUnitTests
{       
    /// <summary>
    ///This is a test class for CustomerRatingBusinessTest and is intended
    ///to contain all CustomerRatingBusinessTest Unit Tests
    ///</summary>
    [TestClass()]
    public class CustomerRatingBusinessTest
    {
        [TestMethod()]
        public void ComputeBestCustomerTest()
        {
            // Get Sample Data
            TestDataRepository objSampleDataRepository = new TestDataRepository();
            // Make an instance of CustomerRatingBusiness using the sample data 
            CustomerRatingBusiness target = new CustomerRatingBusiness(objSampleDataRepository);

            int CustomerOneID = 1; // Customer One
            int CustomerTwoID = 2; // Customer Two

            // Get the best Customer
            CustomerRating_Customer actual = target.ComputeBestCustomer(CustomerOneID, CustomerTwoID);

            // The best customer should be Customer Two
            Assert.IsTrue(actual.CustomerID == 2);
        }

        [TestMethod()]
        public void CompareTheSameCustomer()
        {
            // Get Sample Data
            TestDataRepository objSampleDataRepository = new TestDataRepository();
            // Make an instance of CustomerRatingBusiness using the sample data 
            CustomerRatingBusiness target = new CustomerRatingBusiness(objSampleDataRepository);

            int CustomerTwoID1 = 2; // Customer Two
            int CustomerTwoID2 = 2; // Customer Two

            // Get the best Customer
            CustomerRating_Customer actual = target.ComputeBestCustomer(CustomerTwoID1, CustomerTwoID2);

            // Both Customers are the same so the result should be null
            Assert.IsTrue(actual == null);
        }

        [TestMethod()]
        public void CustomerThreeIsTheBestCustomer()
        {
            // Get Sample Data
            TestDataRepository objSampleDataRepository = new TestDataRepository();
            // Make an instance of CustomerRatingBusiness using the sample data 
            CustomerRatingBusiness target = new CustomerRatingBusiness(objSampleDataRepository);

            int CustomerTwoID = 2; // Customer Two
            int CustomerThreeID = 3; // Customer Three

            // Get the best Customer
            CustomerRating_Customer actual = target.ComputeBestCustomer(CustomerTwoID, CustomerThreeID);

            // Customer Three is always the best Customer
            Assert.IsTrue(actual.CustomerID == 3);
        }
    }
}


From the Test option on the Menu bar in Visual Studio, run the tests.


You will see the results. 

Unit Testing for the rest of us

Unit testing does not have to be hard. The implementation described in this article might not satisfy purists, but it should allow you to achieve your objectives. When faced with creating code that needs to implement complex business rules, it might be helpful to:

This can actually be fun. You write the code and the computer "keeps score" by informing you if the Unit Tests pass or not.

The advantages of using the methods described in this article include:

DotNetNuke module:  CustomerRating_01.00.00_Install.zip (install and source)
Note: If using DotNetNuke 5.1 or lower, install and run LinqPrep first.

[Back to: The ADefWebserver DotNetNuke HELP WebSite]


DotNetNukeŽ is a registered trademark of the DotNetNuke Corporation