Rhino Mocks is the most popular Mock Object Framework for .NET. The purpose of this blog entry is to provide a brief introduction to Rhino Mocks. In particular, I want to describe how you can use Rhino Mocks when building ASP.NET MVC web applications.
In the first part of this entry, I explain why Mock Object Frameworks are critical tools when performing Test-Driven Development. Next, I discuss how you can use Rhino Mocks to create both state verification unit tests and behavior verification unit tests. Finally, I discuss how Rhino Mocks forces you to design your ASP.NET MVC web applications in a particular way.
Why Mock?
Why should you care about Mock Object Frameworks such as Rhino Mocks? If you want to practice Test-Driven Development, then there is no way to avoid using a Mock Object Framework. You need to use a Mock Object Framework to create effective unit tests.
A unit test enables you to test a single unit of code in isolation. Typically, a unit of code corresponds to a single method in your application. Therefore, a unit test should enable you to test just the code inside a particular method and nothing else.
A unit test created for the purposes of Test-Driven Development — a TDD unit test — has additional requirements. A unit test appropriate for TDD unit test should execute very quickly and should not require any application configuration.
When practicing Test-Driven Development, you execute all of your tests whenever you make a code change. Most likely, you’ll execute your unit tests hundreds of times while building an application. If your unit tests are too slow or they take work to setup, then Test-Driven Development will no longer be practical.
Let’s look at a particular code sample. The method in Listing 1 adds two numbers together.
Listing 1 – Calculate.cs
1: using System;
2:
3: public class Calculate
4: {
5:
6: public int AddNumbers(int num1, int num2)
7: {
8: return num1 + num2;
9: }
10: }
Listing 2 contains a unit test for the method in Listing 1. The unit test in Listing 2 tests whether the method in Listing 1 returns the correct value for 2 + 2.
Listing 2 – AddNumbersTest Method
1: [TestMethod]
2: public void AddNumbersTest()
3: {
4: Calculate calculate = new Calculate();
5: int result = calculate.AddNumbers(2, 2);
6: Assert.AreEqual(2 + 2, result);
7: }
There is nothing wrong with the unit test in Listing 2. This unit test enables you to test the AddNumbers() method in isolation. The unit test executes quickly without requiring you to perform any configuration. You could run hundreds of unit tests that look just like this one each time you make a code change without significantly slowing down the process of developing an application.
Unfortunately, most real-world code looks nothing like the code in Listing 1. Most real-world code has dependencies on external objects. In many cases, these external objects have dependencies on resources like databases, configuration files, web services, or the file system.
For example, consider the code in Listing 3. The GetProducts() method in Listing 3 looks more like it was ripped from an actual application.
Listing 3 – GetProducts Method
1: public static List GetProducts()
2: {
3: // Get database connection string
4: string conString = ConfigHelper.GetDBConnectionString();
5:
6: // Get data provider
7: DataProvider dp = new DataProvider(conString);
8:
9: // Return list of products
10: return dp.GetProducts();
11: }
The GetProducts() method references two external classes named ConfigHelper and DataProvider. The ConfigHelper class is used to retrieve a database connection string. The DataProvider class is used to access the database.
The unit test in Listing 4 tests the GetProducts() method.
Listing 4 – GetProductsTest Method
1: [TestMethod]
2: public void GetProductsTest()
3: {
4: List results = Product.GetProducts();
5: Assert.AreEqual(3, results.Count);
6: }
The unit test in Listing 4 checks whether a particular number of products is returned by the GetProducts() method.
Here’s the problem: the test in Listing 4 depends on external resources. These external resources violate the requirements for a good TDD test.
Each time you execute the test in Listing 4, a connection string must be retrieved from the ConfigHelper class. The ConfigHelper class, in turn, retrieves the connection string from a configuration file. The test has a dependency on an external resource. In this case, the external resource is a configuration file. In order to run the unit test, you must have the right configuration file setup in your Test Project.
Furthermore, the unit test in Listing 5 has a dependency on the DataProvider class. The DataProvider class relies on an external resource: a database. In order for the test to run, the product records must be retrieved from a database table. Retrieving data from a database is slow. Imagine running hundreds of tests that must access a database whenever you make a code change. You would never get any work done on your application. You would spend all day waiting for your unit tests to finish executing.
If you want to be able to write an effective test for the GetProducts() method, then you need to take advantage of a Mock Object Framework. A Mock Object Framework enables you to mock external resources such as configuration files and databases. The remainder of this blog entry focuses on a particular Mock Object Framework: Rhino Mocks.
State versus Behavior Verification
A Mock Object Framework such as Rhino Mocks can be used to perform two very different types of unit tests. You can use Rhino Mocks to perform either state or behavior verification (This distinction used to be called state versusinteraction-based testing and many people continue to use these terms).
On the one hand, you can use a Mock Object Framework to create stubs. A stub acts as a stand-in for a normal object. There are multiple reasons that you might create a stub. For example, as discussed above, the actual object might access an external resource such as a database or the file system causing the actual object to be too slow to use in a unit test. The stub enables you to perform a test in a reasonable amount of time without requiring any configuration.
Another and equally valid reason for using a stub is that you haven’t actually implemented a particular object required for a test. For example, you might want to unit test a method that relies on a DataProvider object, but you have not had a chance to implement the data provider object. In this case, the mock object is acting as a stand-in for the real object so that you can build other objects that depend on the stub.
When performing state-based verification, the final statement in your unit test is typically an assert statement that asserts that some condition is true.
When performing behavior verification (an interaction test), on the other hand, you are interested in verifying that a set of objects behave and interact in a particular way. For example, you might want to verify that that when the DataProvider.GetProducts() method is called, a second method named the SqlDataProvider.GetProducts() method is called exactly once with a particular set of parameters or that another method named the SqlDataProvider.ExecuteCommand() method is called exactly three times with another particular set of parameters .
When performing behavior verification, your test typically does not end with an assert statement. Instead, the interaction of the objects being tested is verified. Rhino Mocks throws an exception automatically when mock expectations do not meet reality.
To learn more about state versus behavior verification, and the distinction between mocks and stubs, see Martin Fowler’s article at:
http://martinfowler.com/articles/mocksArentStubs.html
Using Rhino Mocks
Rhino Mocks was created and it is maintained by Ayende Rahien (AKA Oren Eini). You can download Rhino Mocks from the following URL:
http://www.ayende.com/projects/rhino-mocks.aspx
There is a Google group devoted to discussing Rhino Mocks (Ayende Rahien is an active participant) at:
http://groups.google.com/group/RhinoMocks
Rhino Mocks does not include an installer. After you download and unzip Rhino Mocks, you can use Rhino Mocks in a project by adding a reference to the Rhino.Mocks.dll assembly. There is also an XML file included with the download that provides Intellisense for Rhino Mocks within Visual Studio.
One warning about using Rhino Mocks on Windows Vista. Vista will prevent you from using assemblies that you download from the Internet. Before you reference the Rhino.Mocks.dll assembly in Visual Studio, you need to Unblock the assembly: right-click the assembly file and click the Unblock button under the General tab (see Figure 1).
State Verification with Rhino Mocks
You can use Rhino Mocks to create stubs for interfaces, abstract classes, and concrete classes. When creating a stub for class methods, the method must be marked as virtual. This last requirement places some significant limitations on what you can stub. You cannot use Rhino Mocks with static or sealed methods (we’ll return to this issue in the last section of this blog entry).
Let’s start with a simple sample of a stub. Imagine that you have created the abstract base class in Listing 5.
Listing 5 – ProductBase.cs
1: using System;
2:
3: namespace RhinoMockProject
4: {
5: public abstract class ProductBase
6: {
7: public abstract string Name { get; set; }
8:
9: public abstract decimal Price { get; set; }
10:
11: public abstract void Save();
12: }
13: }
You intend, at some point in the future, to create particular types of products that derive from this base class – for example, ElectronicProduct, FoodProduct, SportsProduct — but you haven’t gotten around to creating these concrete classes yet. No worries. By taking advantage of Rhino Mocks, you can pretend that you have already done the work. The test in Listing 6 uses Rhino Mocks to create a concrete instance of the class, set a property, and test the property.
Listing 6 – RhinoMocksTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using RhinoMockProject;
8: using Rhino.Mocks;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class RhinoMocksTest
14: {
15:
16: [TestMethod]
17: public void TestStubAbstract()
18: {
19: // Setup product stub
20: ProductBase product = MockRepository.GenerateStub();
21: product.Name = "Laptop Computer";
22: product.Price = 3200.00m;
23:
24: // Test
25: Assert.AreEqual(3200.00m, product.Price);
26: }
27:
28: }
29: }
In Listing 6, the static method MockRepository.GenerateStub() is called to generate a stub for the abstract ProductBase class. After you generate the stub, you can treat the stub as a normal class and set and read its properties. The last assert statement returns the value True since the stub product’s Price property was, in fact, set to the value $3,200.00.
You also can create stubs for interfaces in the exact same way. For example, Listing 7 contains an IProduct interface.
Listing 7 – IProduct.cs
1: using System;
2:
3: public interface IProduct
4: {
5: string Name { get; set; }
6:
7: decimal Price { get; set; }
8: }
Imagine that you are adding a new feature to your store. You are creating a method that doubles the price of any product. The method is contained in Listing 8.
Listing 8 – ProductManager.cs
1: using System;
2:
3: namespace RhinoMockProject
4: {
5: public class ProductManager
6: {
7:
8: public static void DoublePrice(IProduct product)
9: {
10: product.Price *= 2;
11: }
12:
13: }
14: }
Before you can test the method in Listing 8, you need a class that implements the IProduct interface. If you don’t want to do the work to implement the interface at the moment, then you can use Rhino Mocks to create a stub from the interface automatically. The test in Listing 9, once again, takes advantage of the MockRepository.GenerateStub() method to generate a concrete stub from an interface.
Listing 9 – RhinoMocksTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using RhinoMockProject;
8: using Rhino.Mocks;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class RhinoMocksTest
14: {
15:
16: [TestMethod]
17: public void TestStubInterface()
18: {
19: decimal price = 3200.00m;
20:
21: // Setup product stub
22: IProduct product = MockRepository.GenerateStub();
23: product.Name = "Laptop Computer";
24: product.Price = price;
25:
26: // Call method being tested
27: ProductManager.DoublePrice(product);
28:
29: // Test
30: Assert.AreEqual(price * 2, product.Price);
31: }
32: }
33: }
The test in Listing 9 creates a concrete instance of a class that implements the IProduct interface and passes the instance to the DoublePrice() method. The test demonstrates that the DoublePrice() method does, in fact, double the price of the product correctly.
Setting Up Return Values from Stub Methods
You also can use Rhino Mocks to generate fake return values from classes and interfaces that you have stubbed. Here’s a common scenario. Imagine that you need to test a method that relies on database data. However, you don’t want to access the database when you execute the unit test because accessing the database would be too slow. In this case, you can create a stub method that always returns a hard-coded set of values.
The interface in Listing 10 could be used to represent your data access layer.
Listing 10 – IProductRepository.cs
1: using System;
2: using System.Collections.Generic;
3:
4: public interface IProductRepository
5: {
6: IProduct Get(int ProductId);
7:
8: IEnumerable Select();
9:
10: bool Save(IProduct product);
11: }
The interface in Listing 10 includes a method for retrieving a set of product records, a method for retrieving a particular product record, and a method for saving a product record.
The unit test in Listing 11 illustrates how you can create a stub for the IProductRepository interface and setup the stub’s Select() method so that it always returns a fake set of products.
Listing 11 – RhinoMocksTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using RhinoMockProject;
8: using Rhino.Mocks;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class RhinoMocksTest
14: {
15: private IEnumerable _fakeProducts = new List
16: {
17: new Product {Name = "Steak", Price = 9.85m},
18: new Product {Name = "Milk", Price = 2.02m},
19: new Product {Name = "Diapers", Price = 33.07m}
20: };
21:
22:
23: [TestMethod]
24: public void TestStubInterfaceMethod()
25: {
26: MockRepository mocks = new MockRepository();
27: IProductRepository products = mocks.Stub();
28:
29: using (mocks.Record())
30: {
31: SetupResult.For(products.Select()).Return(_fakeProducts);
32: }
33:
34: var results = products.Select();
35:
36: Assert.AreEqual(3, results.Count());
37: }
38:
39: }
40: }
There are three important differences that you should notice between the code in Listing 11 and the code in the previous samples. First, notice that the sub for the IProductRepository interface is not created by using the GenerateStub() method like in previous code samples. When setting up method return values, you must create an instance of the Rhino Mocks MockRepository class and call the MockRepository instance’s Stub() method.
Second, notice that the SetupResult class is used to setup the return value for the IProductRepository.Select() method. When the Select() method is called, the contents of the _fakeProducts field is returned.
Finally, notice that the call to SetupResult is wrapped in a using statement that references the MockRespository.Record() method. The using statement in conjunction with the Record() method are used to record how the stub should behave when a particular stub method is called. You can setup multiple stub methods within the single using statement.
You can even return different values from a stub method when different parameters are passed to the stub method. For example, the test in Listing 12 returns different products depending on the ProductId passed to the ProductRepository.Get() method.
Listing 12 – RhinoMocksTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using RhinoMockProject;
8: using Rhino.Mocks;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class RhinoMocksTest
14: {
15:
16: [TestMethod]
17: public void TestStubMultipleReturn()
18: {
19: MockRepository mocks = new MockRepository();
20: IProductRepository products = mocks.Stub();
21:
22: using (mocks.Record())
23: {
24: SetupResult
25: .For(products.Get(2))
26: .Return(new Product {Name="Beer", Price=12.99m });
27:
28: SetupResult
29: .For(products.Get(12))
30: .Return(new Product { Name = "Steak", Price = 8.02m });
31: }
32:
33: // Test
34: IProduct product1 = products.Get(2);
35: Assert.AreEqual("Beer", product1.Name);
36:
37: IProduct product2 = products.Get(12);
38: Assert.AreEqual("Steak", product2.Name);
39:
40: IProduct product3 = products.Get(13);
41: Assert.IsNull(product3);
42: }
43:
44: }
45: }
In Listing 12, two different products are returned when calling the Get() method depending on the value of the ProductId. In the Record() section of the test, the two different return values for the Get() method are setup.
Notice the very last assert statement. If you call the Get() method with a ProductId that has not been setup, then the value Null is returned.
If you want any call to the Get() method to always return a particular fake product, then you can call IgnoreArguments() when setting up the return value. This approach is illustrated by the test in Listing 13. As the assert statements at the end of the test method demonstrate, regardless of the ProductId passed to the Get() method, the very same product is returned.
Listing 13 – RhinoMocksTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using RhinoMockProject;
8: using Rhino.Mocks;
9:
10: namespace RhinoMockProjectTests
11: {
12: ///
13: /// Summary description for TestRhino
14: ///
15: [TestClass]
16: public class RhinoMocksTest
17: {
18:
19: [TestMethod]
20: public void TestStubIgnoreArguments()
21: {
22: MockRepository mocks = new MockRepository();
23: IProductRepository products = mocks.Stub();
24:
25: using (mocks.Record())
26: {
27: SetupResult
28: .For(products.Get(1))
29: .IgnoreArguments()
30: .Return(new Product { Name = "Beer", Price = 12.99m });
31: }
32:
33: // Test
34: IProduct product1 = products.Get(2);
35: Assert.AreEqual("Beer", product1.Name);
36:
37: IProduct product2 = products.Get(12);
38: Assert.AreEqual("Beer", product2.Name);
39: }
40:
41: }
42: }
Behavior Verification with Rhino Mocks
You can use Rhino Mocks to verify that a particular set of objects interact in an expected way. For example, you can verify that a particular object method was called the expected number of times with the expected set of parameters. This type of testing is also called interaction-based testing.
This type of test is useful when you cannot verify the state of an object after you interact with the object. For example, an object might not have a public property that you can perform an assert against. Therefore, there is no direct way to validate the state of the object after executing the unit test.
For example, imagine that you need to create a logging component. Every time you perform a significant action in your application, you want to log the action to a log file on disk. (This example of behavior verification is taken from Gerard Meszaros’s xUnit Test Patterns book, see
http://xunitpatterns.com/Behavior%20Verification.html). If the logger component does not have a property that exposes all of its log entries then there will be no easy way to verify whether the logger is behaving as expected.
However, if you can intercept requests to the logger component, then you can verify that other components are interacting with the logger component when you expect. The point of behavior verification is to perform this type of test of the interaction between different components.
Expectations versus Reality
When performing behavior verification tests with Rhino Mocks, your test code is divided into two sections. First, you have a section of code where you record your expectations. Second, you have a section of code where your expectations are tested against reality.
For example, Listing 14 contains a unit test for a logger component. In the first section of the test, a particular set of expectations is recorded. When the Customer.Save() method is called, the Logger.Log() method should be called with the ProductId of the product being saved.
Listing 14 – LoggerTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using Rhino.Mocks;
8: using RhinoMockProject;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class LoggerTest
14: {
15:
16: [TestMethod]
17: public void LogTest()
18: {
19: MockRepository mocks = new MockRepository();
20: Logger logger = mocks.CreateMock();
21: using (mocks.Record())
22: {
23: logger.Log(27);
24: }
25: using (mocks.Playback())
26: {
27: Customer newCustomer = new Customer(27, logger);
28: newCustomer.Name = "Stephen Walther";
29: newCustomer.Save();
30: }
31: }
32: }
33: }
If you run the test in Listing 14, and the Logger.Log() method is not called with the expected value for CustomerId, then the unit test fails. Notice that the unit test does not contain an assert statement. If expectations do not match reality, Rhino Mocks causes the unit test to fail by raising an ExpectationViolationException exception.
The Customer class is contained in Listing 15. Notice that the Logger component is passed to the Customer class in the Customer class constructor. Passing a class in this manner is a type of
dependency injection called
constructor injection(see Martin Fowler,
http://martinfowler.com/articles/injection.html).
Listing 15 – Customer.cs
1: using System;
2:
3: namespace RhinoMockProject
4: {
5: public class Customer
6: {
7: public int Id { get; private set; }
8: public string Name { get; set; }
9: private Logger _logger;
10:
11: public Customer(int Id, Logger logger)
12: {
13: this.Id = Id;
14: _logger = logger;
15: }
16:
17: public void Save()
18: {
19: _logger.Log(this.Id);
20: }
21:
22: }
23: }
The Logger class is contained in Listing 16. This class isn’t really implemented since the Log() method is never really called. The Log() method raises an exception. Since we are mocking the Log() method, this exception is never raised.
Listing 16 – Logger.cs
1: using System;
2:
3: public class Logger
4: {
5: public virtual void Log(int ProductId)
6: {
7: throw new Exception("eeeks!");
8: }
9:
10: }
Strict, Non-Strict, and Partial Mocking
Rhino Mocks supports three different ways to create mock objects. You can mock an object by calling any of the following three methods:
1. CreateMock() – This method of creating a mock object enforces a strict replay of expectations. If a method is called unexpectedly, then the mock object will fail verification and throw an exception.
2. DynamicMock() – This method of creating a mock object permits a looser replay of expectations. If a method is called unexpectedly, then the mock object will not fail verification and no exception is thrown.
3. PartialMock() – This method of creating a mock object can only be performed on classes. Only certain methods are mocked. Other methods of the class can be called normally.
Listing 17 contains a class named Rover. Rover has two methods named Bark() and Fetch(). The unit tests in Listings 18 contrast the three methods of mocking Rover.
Listing 17 – Rover.cs
1: using System;
2:
3: namespace RhinoMockProject
4: {
5: public class Rover
6: {
7:
8: public virtual void Bark(int loudness)
9: {
10: // Make loud noise
11: }
12:
13: public virtual void Fetch(int speed)
14: {
15: // Fetch something
16: throw new Exception("Yikes!");
17: }
18:
19: }
20: }
Notice that both of Rover’s methods are marked as virtual methods. This is a requirement when using Rhino Mocks. When mocking a class, only virtual (and not sealed or static) methods can be mocked.
Notice, furthermore, that the Fetch() method throws an exception. When this method is mocked, this exception won’t be thrown because the actual method won’t ever be called.
Listing 18 – RoverTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using RhinoMockProject;
8: using Rhino.Mocks;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class RoverTest
14: {
15: [TestMethod]
16: public void BarkTestStrictReplay()
17: {
18: MockRepository mocks = new MockRepository();
19: Rover rover = mocks.CreateMock();
20: using (mocks.Record())
21: {
22: rover.Bark(17);
23: LastCall
24: .IgnoreArguments()
25: .Repeat.Times(2);
26: }
27: using (mocks.Playback())
28: {
29: rover.Bark(17);
30: rover.Bark(23);
31: // rover.Bark(2); // fail
32: // rover.Fetch(2); // fail
33: }
34: }
35:
36: [TestMethod]
37: public void BarkTestNonStrictReplay()
38: {
39: MockRepository mocks = new MockRepository();
40: Rover rover = mocks.DynamicMock();
41: using (mocks.Record())
42: {
43: rover.Bark(17);
44: LastCall
45: .IgnoreArguments()
46: .Repeat.Times(2);
47: }
48: using (mocks.Playback())
49: {
50: rover.Bark(17);
51: rover.Bark(23);
52: rover.Bark(2); // pass
53: rover.Fetch(2); // pass
54: }
55: }
56:
57: [TestMethod]
58: public void BarkTestPartialReplay()
59: {
60: MockRepository mocks = new MockRepository();
61: Rover rover = mocks.PartialMock();
62: using (mocks.Record())
63: {
64: rover.Bark(17);
65: LastCall
66: .IgnoreArguments()
67: .Repeat.Times(2);
68: }
69: using (mocks.Playback())
70: {
71: rover.Bark(17);
72: rover.Bark(23);
73: rover.Bark(2); // pass
74: // rover.Fetch(2); // throws exception
75: }
76: }
77:
78: }
79: }
Listing 18 contains three tests that test the same Rover class. The tests differ in how Rover is mocked.
The first test — named BarkTestStrictReplay() — uses CreateMock() to create an instance of Rover. The CreateMock() method enforces strict expectations. If you don’t make the exact method calls setup in the expectations section, then the test fails when the expectations are played back against reality.
For example, in this test, you can’t cause Rover to bark a third time since the expectation was setup that Rover would only bark twice. Also, you can’t call the Fetch() method since no expectations were setup concerning this method.
The next test is named BarkTestNonStrictReplay(). In this test, Rover is created with the DynamicMock() method. This method of creating a mock object allows for looser expectations. Calling the Bark() method a third time, or calling an unexpected method like Fetch(), does not cause the test to fail.
Finally, the third test illustrates how you can mock Rover using the PartialMock() method. The PartialMock() method does not mock all of Rover’s methods and properties. In the test in Listing 18, Rover’s Bark() method is mocked but not his Fetch() method. When Fetch() is called, an exception is thrown since the actual Fetch() method throws an exception (see Listing 17).
Creating Testable Web Applications
In this last section, I want to draw your attention to a controversial topic. If you want to use Rhino Mocks to test an application, then you are forced to write your application in a particular way. Some people think this requirement is a virtue since it forces your application to have a testable design. Other people believe that the design of your application should not be dictated by the limitations of a particular tool.
Rhino Mocks places two important constraints on the design of your application. First, since Rhino Mocks cannot be used to mock static or sealed methods, you are encouraged to avoid both static and sealed methods.
Second, because you cannot mock what you cannot see, you are encouraged to follow a particular pattern called
Dependency Injection when building an application with Rhino Mocks (see Martin Fowler,
http://martinfowler.com/articles/injection.html). There are several variations of the Dependency Injection pattern, but the basic idea is that you should avoid instantiating classes within other classes. Instead, if one class is dependent on other classes, then you should pass these dependencies to the class when the class is initialized. Following this pattern makes it easier to mock the dependencies of a class.
Let’s consider a particular code sample in which both of these limitations of Rhino Mocks is important. Imagine that you decide to create a Logger component for your application. When any method of your application is called, you want to log the action to a file on disk. If I didn’t care about Rhino Mocks, I would use the code in Listing 19 to create the Logger component.
Listing 19 – Logger.cs (First iteration)
1: using System;
2:
3: namespace RhinoMockProject
4: {
5: public class Logger
6: {
7: public static void Write(string message)
8: {
9: // Log message to file system
10: }
11: }
12: }
Notice that the Logger in Listing 19 uses a static method to write to the log file. The advantage of using a static method is that it saves a line of code when calling the method since you don’t need to instantiate the class before using it. For example, the DataProvider class in Listing 20 calls Logger.Write() in both of its methods.
Listing 20 – DataProvider.cs (First Iteration)
1: using System;
2: using System.Collections.Generic;
3:
4: namespace RhinoMockProject
5: {
6:
7: public class DataProvider
8: {
9: public static IEnumerable GetProducts()
10: {
11: Logger.Write("Getting products");
12:
13: // Get products from database
14: return null;
15: }
16:
17:
18: public static bool SaveProduct(Product product)
19: {
20: Logger.Write("Saving new product");
21:
22: // Save product to database
23: return true;
24: }
25:
26:
27: }
28: }
The problem with this approach to building a Logger component is that it is not compatible with Rhino Mocks. The Logger class is not Rhino Mocks mockable for two reasons. First, you cannot mock the Logger.Write() method since it is a static method. Second, you cannot mock the Logger class when it is used within the DataProvider class since the Logger class is not exposed outside of the DataProvider class (you cannot mock what you cannot see).
Listing 21 contains a mockable version of the Logger class. In this revised version, the Write() method is a virtual instance method rather than a static method.
Listing 21 – Logger.cs (Second Iteration)
1: using System;
2:
3: namespace RhinoMockProject
4: {
5: public class Logger
6: {
7: public virtual void Write(string message)
8: {
9: // Log message to file system
10: }
11: }
12: }
The DataProvider class in Listing 22 has been revised to support Dependency Injection. The new DataProvider class accepts an instance of the Logger class in its constructor.
Listing 22 – DataProvider.cs (Second Iteration)
1: using System;
2: using System.Collections.Generic;
3:
4: namespace RhinoMockProject
5: {
6: public class DataProvider
7: {
8: private Logger _logger;
9:
10: public DataProvider(Logger logger)
11: {
12: _logger = logger;
13: }
14:
15: public virtual IEnumerable GetProducts()
16: {
17: _logger.Write("Getting products");
18:
19: // Get products from database
20: return null;
21: }
22:
23: public virtual bool SaveProduct(Product product)
24: {
25: _logger.Write("Saving new product");
26:
27: // Save product to database
28: return true;
29: }
30: }
31: }
Finally, the test in Listing 23 illustrates how you can use Rhino Mocks to perform a behavior verification of the Logger and DataProvider class. Notice that a mock version of the Logger class is created and passed to an actual instance of the DataProvider class.
Listing 23 – LoggerTest.cs
1: using System;
2: using System.Text;
3: using System.Collections.Generic;
4: using System.Linq;
5: using Microsoft.VisualStudio.TestTools.UnitTesting;
6:
7: using Rhino.Mocks;
8: using RhinoMockProject;
9:
10: namespace RhinoMockProjectTests
11: {
12: [TestClass]
13: public class LoggerTest
14: {
15: [TestMethod]
16: public void WriteTest()
17: {
18: MockRepository mocks = new MockRepository();
19: Logger logger = mocks.CreateMock();
20: DataProvider dp = new DataProvider(logger);
21: using (mocks.Record())
22: {
23: logger.Write("Saving new product");
24: logger.Write("Getting products");
25: }
26: using (mocks.Playback())
27: {
28: dp.SaveProduct(null);
29: dp.GetProducts();
30: }
31: }
32:
33: }
34: }
The test in Listing 23 mocks the Logger class and injects the mock into the DataProvider’s constructor. The test sets up the expectations that the Logger.Write() method will be called twice with particular messages. In the Playback section, the DataProvider.SaveProduct() and DataProvider.GetProducts() methods are called, the expectations are matched, and the test passes.
If you are really interested in testable design, then you would most likely not stop here. The next step is to create an interface for the Logger class and pass the interface to the DataProvider constructor instead of the Logger class itself. You should be injecting ILogger instead of a particular logger. Out of laziness, I’ll stop here.
Before ending this section, it is worth mentioning that Typemock Isolator, another Mock Object Framework for .NET, does not have the limitations discussed in this section. You can use Typemock Isolator to mock static methods. Furthermore, you can mock objects without following the Dependency Injection pattern. Some people think this is a virtue of Typemock Isolator and some people think that this is a vice.
Summary
The purpose of this blog entry was to provide you with an introduction to Rhino Mocks. This entry was certainly not intended as a comprehensive discussion of all of the features of Rhino Mocks. The goal was to explain how you can use Rhino mocks to perform both state verification and behavioral verification unit tests. I also discussed some of the important limitations of Rhino Mocks and how these limitations affect how you build applications.