Isolator Extensibility: Properties as Fields

Let’s say I have a class that contains properties:

public class ClassWithProperties
{
public int Number { get; set; }
public List<Product> Products { get; set; }
}

I want to return my own values by faking them and set behavior. I’ll use WhenCalled:

[Isolated]
[TestMethod]
public void PropertiesReadAfterSettingThem()
{
var props = Isolate.Fake.Instance<ClassWithProperties>();

Isolate.WhenCalled(() => props.Number).WillReturn(1);
Isolate.WhenCalled(() => props.Products).WillReturn(
new List<Product> {new Product()});

Assert.AreEqual(1, props.Number);
Assert.AreEqual(1, props.Products.Count);
}

I remember talking to someone about the basics of AAA API, and he wrote the following test (liberally translating to our example), which should do the same:

[Isolated]
[TestMethod]
public void PropertiesReadAfterSettingThem()
{
var props = Isolate.Fake.Instance<ClassWithProperties>();

props.Number = 1;
props.Products = new List<Product> {new Product()};

Assert.AreEqual(1, props.Number);
Assert.AreEqual(1, props.Products.Count);
}

It looks more natural, doesn’t it? Setting properties, instead of using WhenCalled. But Isolator doesn’t support that (yet). Let’s add a function that makes the test pass:

[Isolated]
[TestMethod]
public void PropertiesReadAfterSettingThem()
{
var myClass= Isolate.Fake.Instance<ClassWithProperties>();
IsolatorExtender.ActAsFields(myClass);

myClass.Number = 1;
myClass.Products = new List<Product> {new Product()};

Assert.AreEqual(1, myClass.Number);
Assert.AreEqual(1, myClass.Products.Count);
}

Here’s the ActAsFields function:

public static void ActAsFields<T>(T fake)
{
var mock = MockManager.GetMockOf(fake);
if (mock == null) throw new Exception("Must be fake");

foreach (var property in fake.GetType().GetProperties())
{
if (property.CanRead && property.CanWrite)
{
object fakePropertyValue = null;
mock.AlwaysReturn(property.GetSetMethod().Name,
new DynamicReturnValue(
(p, o) =>
{
fakePropertyValue = p[0];
return null;
}
));
mock.AlwaysReturn(property.GetGetMethod().Name,
new DynamicReturnValue((p, o) => fakePropertyValue));
}
}
}

First of all, everything this function uses is already in Isolator right now. This an example on how to extend Isolator (at least until we do…).

The function does some reflection magic, goes over all the properties and sets the behavior (using Reflective API) for the setters and getters to return the same values. It uses DynamicReturnValue as the return value to do the storing and retrieving.

DynamicReturnValue wraps a delegate, which is invoked when the method we set the behavior on is called. The delegate includes two parameters: p is the parameter list of the method called, and o is the reference to the instance it’s being called on. That means, in the Setter case, p[0] contains the value for the setter. In the Getter, it simply returns the stored value. For each property, there’s a different instance of fakePropertyValue, and so we get storage of the variable. Cool trick.

This simple, yet effective method (concocted in Eli‘s mind) allows you to specify objects’ properties to behave as fields. Look at the powerful combination with using recursive fakes:

[Isolated]
[TestMethod]
public void SharePoint_PropertiesAsFields()
{
SPSite fakeSite = Isolate.Fake.Instance<SPSite>(Members.ReturnRecursiveFakes);

SPList fakeList = fakeSite.OpenWeb().Lists[2];
IsolatorExtender.ActAsFields(fakeList);

fakeList.Title = "fakeList";
Assert.AreEqual("fakeList", fakeList.Title);
}

The ability to grab an item in the chain, and set the value instead of using WhenCalled makes the test much more readable. And we’re all for that. Now, we’re looking for a catchy name, like Grab-n-Set, for this usage. But I’m sure you can come up with a better name.

Care to try?

TOP