PrivateObject have been included in Visual Studio 2005 team edition but since the release of Visual Studio 2008 they have been included in the professional edition, making them available for a wider number of developers.
So what’s the fuss all about? Simply as I’ve stated in earlier posts a PrivateObject allows you to access private members of an object. This can be extremely useful when unit testing an application as combined with mock objects these can create the mechanism that allows tests to be conducted in isolation.
For example, take a look at the class diagram below. In this scenario the person class provides the method CalculatePay this will return the amount of pay an employee should receive. To do this the Person class will make a call to the GetHours() method of a persons TimeSheet. The time sheet is declared as a private variable and is therefore inaccessible to anything outside of the Person class.
So how do we test the CalculatePay method? The TimeSheet class will possibly make calls to a database or an enterprise system that holds this data therefore introducing many possible points of failure, so simply testing all at once won’t give us the full benefit of unit testing, it might tell us something has gone wrong but it can’t tell us what or where, and we all love error message that tell us something has gone wrong contact your administrator, personally I hate this type of error, if something has gone wrong I’d like to know what it is exactly as this can save significant time when fixing the problem. The way we get the specific details of what has gone wrong is isolating the tests, only by isolating the tests and completely controlling the situation can we accurately predict what a method should do, and the way I go about this is using Mock objects and the PrivateObject class.
For more details on Mock Objects see my post on the topic.
In this solution I will make the TimeSheet class implement an interface that defines the GetHours method. I will then create a new class called MockTimeSheet that implements the same interface. However the MockTimeSheet class will return a hard coded value for the hours work, this is important as a hard coded value allows me to know exactly what to expect when I call the method. After defining the mock object I will use the PrivateObject to update the private fields of the Person class before running the test.
The class diagram below gives an overview of what the solution will now look like.
We’ll start to implement this example and as always we’ll start by implementing the unit tests first.
- Open Visual Studio 2008 and create a new blank solution called “PrivateObjectExample”.
- Add a new test project called PayrollTests
- Add a new class library project with a single empty public class called Person. The solution should now have the following items.
- Rick click on the PayrollTests project and select Add -> Unit Test
- Tick the tick box beside the Payroll project icon and click ok.
- We now want to call the CalculatePay method in a controlled manner so that we know exactly what to expect and therefore what to test against. To do this we will need to set the hourly rate and control the number of hours that the TimeSheet class returns. This is where mock objects come into play. Add a new interface to the Payroll project called ITimeSheet which has one method GetHours. The code snippet below shows what the interface should look like.
- We will now create a mock object that implements the ITimeSheet interface. Because the mock object is specific to testing it is therefore added to the PayrollTests projects. Right click on the PayrollTests project and select add new class. Call the class MockTimeSheet and implement the ITimeSheet interface the code below shows what you should have.
public class MockTimeSheet: ITimeSheet
public double GetHours()
- We can now start to define the test. To start with we create an instance of the Person class and set the HourlyRate property to £5 per hour. At this point we now need to set the class to use the mock time sheet. This is done by creating an instance of the PrivateObject class and calling the SetField method. The set field method takes two parameters, a string which is the name of the variable you wish to set and the second parameter is the value you wish to assign to the private field. After the private field has been set it is simply a matter of calling the CalculatePay method of Person class and using the Assert class to test that the calculated pay matches what is expected. The code snippet below shows the code for the test method. Just for interest note the call to the Assert.AreEqual takes three parameters, this third parameter is a message that is displayed when the test fails.
public void CalculatePayTest()
Person person = new Person();
person.HourlyRate = 10.00;
PrivateObject po = new PrivateObject(person);
po.SetField("_timeSheet", new MockTimeSheet());
double pay = person.CalculatePay();
Assert.AreEqual(38, pay, "Calculated pay does not match the expected pay!");
- You can download the complete solution here.
PrivateObjectExample.zip (60.25 KB)