Let’s say I have a class that contains properties:
1 |
<span class="kwrd">public</span> <span class="kwrd">class</span> ClassWithProperties<br />{<br /> <span class="kwrd">public</span> <span class="kwrd">int</span> Number { get; set; }<br /> <span class="kwrd">public</span> List<Product> Products { get; set; }<br />} |
I want to return my own values by faking them and set behavior. I’ll use WhenCalled:
1 |
[Isolated]<br />[TestMethod]<br /><span class="kwrd">public</span> <span class="kwrd">void</span> PropertiesReadAfterSettingThem()<br />{<br /> var props = Isolate.Fake.Instance<ClassWithProperties>();<br /><br /> Isolate.WhenCalled(() => props.Number).WillReturn(1);<br /> Isolate.WhenCalled(() => props.Products).WillReturn(<br /> <span class="kwrd">new</span> List<Product> {<span class="kwrd">new</span> Product()});<br /><br /> Assert.AreEqual(1, props.Number);<br /> Assert.AreEqual(1, props.Products.Count);<br />} |
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:
1 |
[Isolated]<br />[TestMethod]<br /><span class="kwrd">public</span> <span class="kwrd">void</span> PropertiesReadAfterSettingThem()<br />{<br /> var props = Isolate.Fake.Instance<ClassWithProperties>();<br /> <br /> props.Number = 1;<br /> props.Products = <span class="kwrd">new</span> List<Product> {<span class="kwrd">new</span> Product()};<br /><br /> Assert.AreEqual(1, props.Number);<br /> Assert.AreEqual(1, props.Products.Count);<br />} |
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:
1 |
[Isolated]<br />[TestMethod]<br /><span class="kwrd">public</span> <span class="kwrd">void</span> PropertiesReadAfterSettingThem()<br />{<br /> var myClass= Isolate.Fake.Instance<ClassWithProperties>();<br /> IsolatorExtender.ActAsFields(myClass);<br /><br /> myClass.Number = 1;<br /> myClass.Products = <span class="kwrd">new</span> List<Product> {<span class="kwrd">new</span> Product()};<br /><br /> Assert.AreEqual(1, myClass.Number);<br /> Assert.AreEqual(1, myClass.Products.Count);<br />} |
Here’s the ActAsFields function:
1 |
<span class="kwrd">public</span> <span class="kwrd">static</span> <span class="kwrd">void</span> ActAsFields<T>(T fake)<br />{<br /> var mock = MockManager.GetMockOf(fake);<br /> <span class="kwrd">if</span> (mock == <span class="kwrd">null</span>) <span class="kwrd">throw</span> <span class="kwrd">new</span> Exception(<span class="str">"Must be fake"</span>);<br /><br /> <span class="kwrd">foreach</span> (var property <span class="kwrd">in</span> fake.GetType().GetProperties())<br /> {<br /> <span class="kwrd">if</span> (property.CanRead && property.CanWrite)<br /> {<br /> <span class="kwrd">object</span> fakePropertyValue = <span class="kwrd">null</span>;<br /> mock.AlwaysReturn(property.GetSetMethod().Name,<br /> <span class="kwrd">new</span> DynamicReturnValue(<br /> (p, o) =><br /> {<br /> fakePropertyValue = p[0];<br /> <span class="kwrd">return</span> <span class="kwrd">null</span>;<br /> }<br /> ));<br /> mock.AlwaysReturn(property.GetGetMethod().Name,<br /> <span class="kwrd">new</span> DynamicReturnValue((p, o) => fakePropertyValue));<br /> }<br /> }<br />} |
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:
1 |
[Isolated]<br />[TestMethod]<br /><span class="kwrd">public</span> <span class="kwrd">void</span> SharePoint_PropertiesAsFields()<br />{<br /> SPSite fakeSite = Isolate.Fake.Instance<SPSite>(Members.ReturnRecursiveFakes);<br /><br /> SPList fakeList = fakeSite.OpenWeb().Lists[2];<br /> IsolatorExtender.ActAsFields(fakeList);<br /><br /> fakeList.Title = <span class="str">"fakeList"</span>;<br /> Assert.AreEqual(<span class="str">"fakeList"</span>, fakeList.Title);<br />} |
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?