.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


comments powered by Disqus

Featured

  • VS Code Copilot Previews New GPT-4o AI Code Completion Model

    The 4o upgrade includes additional training on more than 275,000 high-quality public repositories in over 30 popular programming languages, said Microsoft-owned GitHub, which created the original "AI pair programmer" years ago.

  • Microsoft's Rust Embrace Continues with Azure SDK Beta

    "Rust's strong type system and ownership model help prevent common programming errors such as null pointer dereferencing and buffer overflows, leading to more secure and stable code."

  • Xcode IDE from Microsoft Archrival Apple Gets Copilot AI

    Just after expanding the reach of its Copilot AI coding assistant to the open-source Eclipse IDE, Microsoft showcased how it's going even further, providing details about a preview version for the Xcode IDE from archrival Apple.

  • Introduction to .NET Aspire

    Two Microsoft experts will present on the cloud-native application stack designed to simplify the development of distributed systems in .NET at the Visual Studio Live! developer conference coming to Las Vegas next month.

  • Microsoft Previews Copilot AI for Open-Source Eclipse IDE

    Catering to Java jockeys, Microsoft is yet again expanding the sprawling reach of its Copilot-branded AI assistants, previewing a coding tool for the open-source Eclipse IDE.

Subscribe on YouTube

Upcoming Training Events