Date Published: July 19, 2021
When testing a class, you often need to assign a value to a property - a property which you wouldn't otherwise want to allow to be set. In this scenario, reflection can be a valuable tool.
Reflection is a tool in C# that can be used to access the metadata of a class. It has a number of uses, but it should generally be used as a last resort, as there are some drawbacks. Using reflection can lead to some performance limitations. More importantly, reflection - as you'll see below - allows you to access properties marked as private, protected, internal, etc. Theoretically, those properties were marked as such for a reason, so it's not necessarily a great idea to go and mess with that encapsulation.
However, in this case, we have a good reason: testing. Test code can bend rules that production code cannot. Below is a good use-case scenario for reflection.
Imagine a Member
class in a gym's user management appliection. A Member
probably has some basic properties like Name
, Email
, SubscriptionPlan
, etc. Another important property might be DateJoined
.
public class Member
{
public string Name {get; set;}
public string Email {get; set;}
public SubscriptionPlan SubscriptionPlan {get; set;}
public DateTime DateJoined {get; private set;}
public Member(string name, string email, SubscriptionPlan subscriptionPlan)
{
Name = name;
Email = email;
SubscriptionPlan = subscriptionPlan;
DateJoined = DateTime.Today;
}
// other code here
}
Above, DateJoined
has a private setter and is set to DateTime.Today
in the constructor. There is virtually no scenario in which you would want to change the value of DateJoined
. Therefore, there is no reason to give this property a public setter and it is probably a bad idea to do so.
Alright, fair enough. But what if I want to test the CheckIfMemberNeedsAnniversaryGift
method of AnniversaryGiftService
? Now things get tricky.
public class AnniversaryGiftService
{
public void SendGiftIfNeeded(Member member)
{
if (CheckIfMemberNeedsAnniversaryGift(member)) SendAnniversaryGift();
}
public bool CheckIfMemberNeedsAnniversaryGift(Member member)
{
return (member.DateJoined.Day == DateTime.Today.Day &&
member.DateJoined.Month == DateTime.Today.Month);
}
public void SendAnniversaryGift()
{
// some code
}
}
As you can see, the logic of CheckIfMemberNeedsAnniversaryGift
is entirely dependent on member
's DateJoined
. But as discussed above, DateJoined
has a private setter. So how can we test this method?
You guessed it - reflection!
One implementation of reflection, which we will use here, involves using extension methods to set the private DateJoined
property of Member
:
public static class ReflectionHelperExtensionMethods
{
// from https://stackoverflow.com/a/1565766/13680266
public static void SetPrivateDateTimePropertyValue(this Member member, string propName, DateTime newValue)
{
PropertyInfo propertyInfo = typeof(Member).GetProperty(propName);
if (propertyInfo == null) return;
propertyInfo.SetValue(member, newValue);
}
}
Essentially, the above code scans the metadata of the Member
class to find a property of the name passed in. In this case, we would pass "DateJoined"
, and then the SetValue
method above would set that property to newValue
, without regard for that property being marked private
.
If you're unfamiliar with extension methods, this article provides a nice overview of their use in tests.
Finally, here is the test we can write now that the reflection extension method is set up:
public static class AnniversaryGiftServiceCheckIfMemberNeedsAnniversaryGift
{
// other code omitted
[Test]
public void ReturnsTrueGivenMemberWithDateJoined1YearAgo
{
var testMember = new Member("testName", "testEmail", "testSubscriptionPlan");
// use reflection to set DateJoined property
testMember.SetPrivateDateTimePropertyValue("DateJoined", DateTime.Today.AddYears(-1));
var memberNeedsAnniversaryGift = _anniversaryGiftService.CheckIfMemberNeedsAnniversaryGift(testMember);
Assert.True(memberNeedsAnniversaryGift);
}
}
This was a pretty simple use case. In fact, we could have omitted the propName
parameter of SetPrivateDateTimePropertyValue
and renamed it to SetDateJoinedPropertyValue
:
// from https://stackoverflow.com/a/1565766/13680266
public static void SetDateJoinedPropertyValue(this Member member, DateTime newValue)
{
PropertyInfo propertyInfo = typeof(Member).GetProperty("DateJoined");
if (propertyInfo == null) return;
propertyInfo.SetValue(member, newValue);
}
However, this diminishes flexibility later on if Member
gets more complex and has more DateTime
properties with private setters, like a DateOfSubscriptionExpiration
, for example. The propName
parameter allows us to use the above method with more flexibility in a more complex and less contrived use case.
The StackOverflow thread that explains how to use reflection to set a private property, as we did above.
A C# Corner article explaining reflection and its uses in detail.
Thanks for reading! I hope you find this and other articles here at ilyanaDev helpful! Be sure to follow me on Twitter @ilyanaDev.