DotNetSurfers

Latish Sehgal's Blog

Getting Started With Mocking - Part 1 - the Basics

Download Code.

This 2 part blog post series is based on a talk I gave at the Dallas C# SIG last month. In this post, I describe general mocking concepts that are independent of mocking frameworks. In the next post, I’ll describe how using a mocking framework (like MOQ) can reduce development time and effort.

You are probably familiar with Unit Tests, but let’s spend a little time revisiting them. As per Roy Osherove

              “A unit test is a fast, in-memory, consistent, automated and repeatable test of a functional unit-of-work in the system.”

This is different from writing Integration Tests, which can talk to external resources such as the filesystem, webservices, database and are inherently slow. A common mistake is to create a Unit Test Suite but fill it with Integration tests. This increases the test execution time and discourages developers from running the Unit Tests on a regular basis (Ideally, each time before checking in code or more frequently).

For the purpose of this series, lets assume that you are a senior developer at a firm with plenty of work on your hands. Your pointy haired manager stops by to tell you that he has a new project for you. Seeing that you are busy, he offers to assign you an intern, Adam. You reluctantly agree and talk to Adam who seems like a sharp, reserved guy with a good grasp over OOPs concepts. You go over the project requirements with him, which consist of building a simple Shopping Cart. The Shopping Cart should

  1. have the capability to add a product and update price accordingly,
  2. have some sort of logging functionality. Adam seems confident that he can build this project, When asked about Unit Testing, he admits not having any prior experience with it. You tell him to look it up, and write Unit Tests for his code as well. So Adam goes into his cave, and starts working on the Shopping Cart app.He informs you after 2 days that he has now completed the application and his code is ready for review. The basic design looks like this

MOQ_ClassDiagram[1]

The code for the ShoppingCart class is pretty simple. It has a Total property denoting the total price of the Shopping Cart, and an AddProduct() function, which adds the price of the product to the total and logs a message. The logger object is passed to the ShoppingCart via constructor injection. Also, the ShoppingCart is coded against IProduct and ILogger interfaces rather than concrete implementations, which makes testing easier.

   1:   public class ShoppingCart




   2:      {




   3:          public ShoppingCart(ILogger logger)




   4:          {




   5:              this._logger = logger;




   6:          }




   7:   




   8:          private ILogger _logger;




   9:          public decimal Total { get; set; }




  10:   




  11:          public void AddProduct(IProduct product)




  12:          {




  13:              Total = Total + product.Price;




  14:              if (_logger != null)




  15:                  _logger.Log(String.Format("Product {0} has been added.",product.Name));




  16:          }




  17:      }

We don’t really care about the Logger and Product implementation, but just know that the Logger logs to a text file in “C:\temp”, and the Product class retrieves the product’s price from the database. Adam has created 2 unit tests, one to check that the Shopping Cart updates its price correctly, and the second one to ensure that logging is done.

   1:          [TestMethod]




   2:          public void AddProduct_AddingProductWithPrice10_ShouldMakeTotal10()




   3:          {




   4:              var target = new ShoppingCart(null);




   5:              //make sure product with ID =1 in database has Price = 10.00




   6:              var product = new Product {ID = 1, Name = "Product1"};




   7:              target.AddProduct(product);




   8:              Assert.AreEqual(target.Total, 10.00M);




   9:          }




  10:   




  11:          [TestMethod]




  12:          public void AddProduct_AddingProduct_ShouldCallLogger()




  13:          {




  14:              var logger = new Logger();




  15:              var target = new ShoppingCart(logger);




  16:              var product = new Product {ID = 2, Name = "Product2"};




  17:              target.AddProduct(product);




  18:              //Open log file and make sure corresponding log was added.




  19:          }

Clearly, his Unit Tests leave a lot to be desired. They depend on the state of the database and the filesystem and therefore are not fast or in-memory. For the same reasons, they are also not repeatable or automated. These are in fact, more close to Integration Tests than Unit Tests. This is a very common mistake. You advise him to mock his dependencies, and since the ShoppingCart class interacts with interfaces rather than concrete implementations, this should be easy to accomplish.

Stubs VS Mocks

The definition of stubs and mocks and their differences has been debated many times before. The Mocks Aren’t Stubs article by Martin Fowler is a good resource to get started (The concepts really hit home for me after reading Roy Osherove’s The Art of Unit Testing. I highly recommend the book if you are getting started with Unit Testing). A few guidelines to remember about Mocks and Stubs are:

  • You can have many stubs in a unit test, but you should have only one mock object.

  • Stubs help in getting the unit test set up, but you’ll never assert against the stub object. You’ll assert against the class under test. 

stub[1]

If you are using mocks, you will assert against the mock object. Stubs lend themselves more naturally to state based unit testing and mocks to interaction based unit testing.

mock[1]

Adam now understands what he is doing wrong, and works on isolating his unit tests to test only the ShoppingCart class. He creates new implementations for IProduct and ILogger that he can use for Unit Testing. Note that in the new implementations, he only has to put in logic needed to get his unit tests to work.

   1:      public class ProductStub:IProduct




   2:      {




   3:          public string Name { get; set; }




   4:          public decimal Price { get; set; }




   5:          public string GetProductCategory()




   6:          {




   7:              throw new NotImplementedException();




   8:          }




   9:      }




  10:      public class LoggerMock:ILogger




  11:      {




  12:          public int NumberOfTimesLoggerCalled { get; set; }




  13:          public void Log(string text)




  14:          {




  15:              NumberOfTimesLoggerCalled++;




  16:          }




  17:      }

And he revises his Unit Tests accordingly

   1:          [TestMethod]




   2:          public void AddProduct_AddingProductWithPrice10_ShouldMakeTotal10()




   3:          {




   4:              var cart = new ShoppingCart(null);




   5:              var stubProduct = new ProductStub(){Price=10.00M};




   6:              cart.AddProduct(stubProduct);




   7:              Assert.AreEqual(cart.Total, 10.00M);




   8:          }




   9:   




  10:          [TestMethod]




  11:          public void AddProduct_AddingProduct_ShouldCallLogger()




  12:          {




  13:              var loggerMock = new LoggerMock();




  14:              var cart = new ShoppingCart(loggerMock);




  15:              int oldNumberOfTimes = loggerMock.NumberOfTimesLoggerCalled;




  16:              cart.AddProduct(new ProductStub());




  17:              Assert.AreEqual(oldNumberOfTimes + 1, loggerMock.NumberOfTimesLoggerCalled);




  18:          }

Adam’s Unit Tests now pass and he has done a good job of keeping them free from external resources such as the filesystem or database by manually mocking his classes. Note how the first Unit Test uses the new IProduct implementation (a Stub) to support the Unit Test but asserts against the Shopping Cart object, whereas the second one asserts against the new ILogger implementation (a Mock).

In the next post, we’ll see how Adam can achieve the same granularity in his Unit Tests by using a mocking framework rather than creating manual mocks.

Download Code.

Part 2.

Comments