Practical .NET

Ensure Consistent Testing with Mock Objects and Moq

If you run an automated test and your test fails then you want to know that it's your fault -- not a problem in someone else's code or the result of a change in your test data. Moq lets you do that in two lines of code, even if your code uses the ASP.NET Session object.

In an earlier column, I discussed what's required to do automated testing with ASP.NET MVC Controllers. But there's some testing you should be doing before you even get to testing your controllers.

A typical Action method in a Controller will have code like this, for example:

Function Index(CustId As String, TotalSale As Decimal) As ActionResult
Dim cust As Customer
  cust = cra.GetCustomerById(CustId)
  If CustomerSalesManagement.ProcessSale(cust, TotalSale) Then

There's not really much point in testing this Index method until you're sure the ProcessSale method works. A divide-and-conquer strategy that tests objects lower in the hierarchy before testing code that depends on them helps keep your tests simple.

That ProcessSales method probably looks something like this:

Public Function ProcessSale(cust As Customer, TotalSales As Decimal) As Boolean
  If cust.CheckCredit(TotalSale) Then
    Return UpdateCustomerCredit(cust)
  End If
  Return False
End If

To test this ProcessCustomer method I need a Customer object that will pass the CheckCredit test so that I can run the class's private UpdateCustomerCredit method. In other words, I need a Customer object correctly configured to support a particular test.

Rather than pull data from the database, I'd prefer to create a mock customer object that I can configure from my test code (if I pull data from the database, what will happen to my test if someone changes or deletes my test data?). I can create a mock object by instantiating a real Customer object and setting its properties, but I'd prefer to use Moq.

My reasons for preferring Moq are simple: It's the easiest/fastest way to create test data. A real Customer object might insist that I set more properties on the object than I need for my test, forcing me to write additional code; internal code in a real Customer object might require that specific properties be consistent with each other, making it more difficult for me to configure the object. Using Moq allows me to arbitrarily set or omit the properties and methods so that I only have to write the code I need for a test. As long as I'm not testing the Customer object itself then using a mock Customer object created with Moq is usually easier than using the real thing. The same is true of the built-in ASP.NET object, like the Session object.

Mocking Methods and Parameters
My first step in building a test for ProcessSale is to create a mock Customer object with a CreditCheck method that always returns True -- that will ensure my mock object passes the test inside ProcessSale and goes on to exercise the "real" code. Using Moq, I create a Mock object based on my Customer object and then use that mock object's Setup method to specify what's to be returned when the CheckCredit method is called.

This code does that, specifying that a value of True should be returned when the CheckCredit method is passed a value of 1,000 (if I pass a different value, the default value for the CreditCheck's data type will be returned):

Dim custMock As New Mock(Of Customer)
custMock.Setup(Function(c) c.CheckCredit(1000)).Returns(True)

For this code to work, though, the CheckCredit method in the real Customer object has to be marked as Overridable (in Visual Basic) or virtual (in C#). But, once I've done that, I can run a test using my mock object:

Dim res As Boolean
res = ProcessSale(custMock.Object, 1000)
Assert.AreEqual(False, res, "Bad order not rejected")

This pattern is typical for Moq: One line of code to instantiate the Mock object, one line of code to specify the return value for a method (or a property, as you'll see later).

If I don't care what input parameter is passed to the CreditCheck method, I can use the Moq It object, passing the It object's IsAny property as the parameter to my mock method. This example will always return True no matter what Decimal value is passed to the CheckCredit method:

custMock.Setup(Function(c) c.CheckCredit(It.IsAny(Of Decimal))).Returns(True)

And, if I want to actually process the input value inside my Moq method, I can do that by passing a lambda expression to the Returns method. This code will test the value passed to CheckCredit and return True or False depending on whether the value is less than 1,000:

custMock.Setup(Function(c) c.CheckCredit(It.IsAny(Of Decimal))).
                                     Returns(Function(a As Decimal)
                                                If a < 1000 Then
                                                   Return True
                                                Else
                                                   Return False
                                                End If
                                              End Function)

I have to admit, though, that I prefer mock methods that return fixed values to mock methods that actually process inputs. Once I put code in my mock method I start feeling that I should be testing that code, not counting on that code for testing purposes. If I need my mock Customer objects to return different values for different tests, I would prefer to create a new mock Customer object for each test rather than create a more complicated one. And if I need my mock Customer object to return different values in the same test … well, then, I'm probably trying to do too much in that test.

Mocking Properties
However, when I look at the UpdateCustomerCredit method, I discover that the method uses several properties belonging to the Customer object passed to it. For example, the UpdateCustomerCredit method depends on the Customer's CreditLimit property so, for me to test UpdateCustomerCredit, I also need to have my mock Customer object return a value from the CreditLimit property.

To establish a value to be returned from a property, you use the Moq SetupGet method. The following code creates a CreditLimit property on the mock Customer object that always returns 500 (again, the CreditLimit property in the real Customer object will need to be marked as virtual or overridable):

custMock.SetupGet(Function(c) c.CreditLimit).Returns(500)

It also turns out, however, that the UpdateCustomerCredit method uses the CreditLimit property on the Customer's Parent property. Specifically, the code in UpdateCustomerCredit looks something like this:

Dim TotalLimit As Decimal
If cust.Parent IsNot Nothing Then
  TotalLimit = cust.CreditLimit + cust.Parent.CreditLimit
End If

Therefore, for at least some of my tests of the UpdateCustomerCredit method, I'm going to need to have a value returned from the Parent property's CreditLimit property. That's equally easy to do with Moq (at least, once you've marked the Parent property as Overridable/virtual). This SetupGet code specifies that the CreditLimit property on the object in the Parent property will always return 2,000:

custMock.SetupGet(Function(c) c.Parent.CreditLimit).Returns(2000)  

Built-in Objects
You're not limited to mocking your own classes. If you have code that uses the ASP.NET Session object then you can use the Moq tools you've already seen to specify what's to be returned from the Session object (though it does require four lines of code). First, create a mock of the ControllerContext object that ASP.NET uses to hold the Session object. Then, set the Session property on that ControllerContext object to the value you want returned, specifying the key value the Action method uses. Finally, put that mock ControllerContext object in the ControllerContext property of the Controller before calling the Action method you want to test.

Putting that all together, a test that puts a mock Customer object in the Session object under the key CurrentCustomer before calling the Action method would look like this:

Dim hc As HomeController
hc = New HomeController()

Dim controllerContext As New Mock(Of ControllerContext)
controllerContext.SetupGet(Function(c As ControllerContext) c.HttpContext.Session("CurrentCustomer")).Returns(custMock.Object)
hc.ControllerContext = controllerContext.Object

Dim res As ViewResult
res = CType(hc.Index(), ViewResult)

The nice thing about this code is that you don't have to make any effort to pass your mock Session object to the Action method you want to test it in. Furthermore, this code works regardless of whether the Action method uses the Controller class' Session property or the ControllerContext.Session property to access Session data.

I'm obviously fond of Moq but, I gather, all of the testing frameworks are equally as good. (I've only used two or three and Moq is the only one I've used for any period of time.) The important thing is whether you want to test at all, not which mocking framework you use. Once you've made the decision to test, automated testing (and mock objects) is the right way to do it. The only simpler solution is not to test at all.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube