Visual Studio uses Publicize to create accessors public
for private
members and types of a type.
But when you try to set elements of a private
array of elements of a private
type, things get complicated.
Imagine this hypothetic class to test:
public static class MyClass
{
private static readonly MyInnerClass[] myArray = new MyInnerClass[10];
public static bool IsEmpty()
{
foreach (var item in myArray)
{
if ((item != null) && (!string.IsNullOrEmpty(item.Field)))
{
return false;
}
}
return true;
}
private class MyInnerClass
{
public string Field;
}
}
If I want to write a test for the case when the array has “non empty” entries, I need to setup the array first.
Using the accessors generated by Visual Studio, I would write something like this:
[TestClass()]
public class MyClassTest
{
[TestMethod()]
public void IsEmpty_NotEmpty_ReturnsFalse()
{
for (int i = 0; i < 10; i++)
{
MyClass_Accessor.myArray[i] =
new MyClass_Accessor.MyInnerClass { Field = i.ToString() };
}
bool expected = false;
bool actual;
actual = MyClass.IsEmpty();
Assert.AreEqual(expected, actual);
}
}
But the test will fail because, although the elements of the private
array myArray
can be read as MyClass_Accessor.MyInnerClass
instances, they can't be written as such.
To do so, the test would have to be written like this:
[TestClass()]
public class MyClassTest
{
[TestMethod()]
public void IsEmpty_NotEmpty_ReturnsFalse()
{
for (int i = 0; i < 10; i++)
{
MyClass_Accessor.ShadowedType.SetStaticArrayElement
("myArray", new MyClass_Accessor.MyInnerClass
{ Field = i.ToString() }.Target, i);
}
bool expected = false;
bool actual;
actual = MyClass.IsEmpty();
Assert.AreEqual(expected, actual);
}
}
But, this way, we lose all the strong typing of the accessors because we need to write the name of the array field.
Because the accessor for the field is a property, we could write a set of extension methods that take care of getting the field name for us. Something like this:
public static class PrivateypeExtensions
{
public static void SetStaticArrayElement<T>
(this PrivateType self, Expression<Func<T[]>> expression,
T value, params int[] indices)
{
object elementValue = (value is BaseShadow) ?
(value as BaseShadow).Target : value;
self.SetStaticArrayElement(
((PropertyInfo)((MemberExpression)(expression.Body)).Member).Name,
elementValue,
indices);
}
public static void SetStaticArrayElement<T>
(this PrivateType self, Expression<Func<T[]>>
expression, BindingFlags invokeAttr, T value, params int[] indices)
{
object elementValue = (value is BaseShadow) ?
(value as BaseShadow).Target : value;
self.SetStaticArrayElement(
((PropertyInfo)((MemberExpression)(expression.Body)).Member).Name,
invokeAttr,
elementValue,
indices);
}
}
Now, we can write the test like this:
[TestClass()]
public class MyClassTest
{
[TestMethod()]
public void IsEmpty_NotEmpty_ReturnsFalse()
{
for (int i = 0; i < 10; i++)
{
MyClass_Accessor.ShadowedType.SetStaticArrayElement(() =>
MyClass_Accessor.myArray, new MyClass_Accessor.MyInnerClass
{ Field = i.ToString() }, i);
}
bool expected = false;
bool actual;
actual = MyClass.IsEmpty();
Assert.AreEqual(expected, actual);
}
}
It’s not the same as the first form, but it’s strongly typed and we'll get a compiler error instead of a test run error if we change the name of the myArray
field.
You can find this and other tools on the PauloMorgado.TestTools on CodePlex.