.NET Tips and Tricks

Blog archive

.NET Tip: Testing Private Fields

If you're interested in this tip, skip the next section -- the next section is just a long-winded explanation of the most recent occasion when I felt I had to use these tools. You can tell, by the length of my apology, that I'm uncomfortable with this tip.

An Apology
Here's my problem: I have a data repository that retrieves data and, after returning the data to the client, holds the data in a cache so that the next request for the same data will run much faster. The cache is a List of objects declared as a field/class-level variable ... and that field is declared as private because the cache is no business of the client and shouldn't be accessible. I should be able to test my repository without considering the cache, right? Either my test gets the right answer or it doesn't and I shouldn't care if the private cache was involved.

Except I do care. For one thing, if the data repository just ignores the cache, then my code is failing. Among other goals, I want my tests to make sure that the cache is being used when appropriate. In addition, all my data retrieval methods accept a parameter that clears the cache. This feature allows the user to choose to get the latest, freshest data from the database. To test that option, I need to know when the cache is cleared.

There are lots of ways of checking for that. I could create a mock database object that would report when the database is accessed (and, by implication, when the cache is being used). However, since I'm using Entity Framework, that would require mocking Entity Framework ... which requires more work than just checking the private field holding the cache (and the more code I write, the more likely it is that my test will have a bug in it). Alternatively, I could insert data into the database to make the database different from the cache and check for those differences ... except this repository is working with a read-only data source (it's actually the output of a stored procedure that assembles several tables).

The Actual Tip
If you want to check a private field during testing, you can use either PrivateObject or PrivateType. PrivateType is the tool to use when accessing static members (that is, members you can call directly from the class name, like Integer.TryParse()); PrivateObject is the tool to use when accessing instance members (that is, members that can only be accessed from an instantiated class like, firstName.Substring(1,3)).

To use PrivateObject, you instantiate the class, passing the object whose private members you're interested in examining. You can then use that PrivateObject object to access internal members by passing the member's name as a string to, for example, the GetField method. You'll need to do a cast to convert the returned reference to the right type.

This code passes a CustomerRepository class to PrivateObject and then retrieves a field called DataCache, cast as a List of Customer objects:

CustomerRepository cr = new CustomerRepository();
PrivateObject po = new PrivateObject(cr);
List<Customer> cachedData = (List<Customer>)po.GetField("DataCache");
cachedData.Add(new Customer { Id = -99 });

With PrivateObject you can also retrieve properties, elements of an internal array, invoke a private method, or get the Type object for the for the object that PrivateObject is wrapping.

If I wanted to access a static member, I'd use PrivateType, passing the Type object for the class I'm testing. This code retrieves a static version of the DataCache field in the CustomerRepository class:

PrivateType pt = new PrivateType(typeof(CustomerRepository));
List<Customer> cachedData = (List<Customer>)pt.GetStaticField("DataCache");
cachedData.Add(new Customer { Id = -99 });

Of course, by using this technique I'm just asking for trouble: If I ever change the internal implementation of my cache, this test will stop working and not because there's a bug in my code. So far, I'm OK with that ... but you should check in a couple of years from now. You may find some later programmer cursing both my tests and my name.

Posted by Peter Vogel on 10/03/2018 at 10:29 AM

comments powered by Disqus