Advanced BeanProxy Examples

Review the BeanProxy Disclaimer

I look forward to filling this section with awesome displays of BeanProxy's power. For a more complete set of examples refer to the unit tests provided with the BeanProxy solution source code http://beanproxy.codeplex.com/SourceControl/list/changesets.

Supported Parameters

BeanProxy is easiest to use with Action's (void return type functions) and Func's (non-void type functions) containing 0 to 5 non-ref, non-out parameters.

Parameters can be:

1. Nullable, such as
  public virtual void Foo(int? age)
  {
  }

2. Templated (I know c# calls them generic, but I think that's a stupid name), such as
  public virtual void Foo(List<int> ages)
  {
  }

3. Arrays, such as
  public virtual void Foo(int[] ages)
  {
  }

4. Params, such as
  public virtual void Foo(params int[] ages)
  {
  }

5. Mixtures of all Nullable, arrays, and templated, and params. Such as
  public virtual void Foo(List<int?>[] ages, params int[] other)
  {
  }

Supported scope access

BeanProxy can only configure virtual functions. Therefore, it can only configure a function if it is public virtual or protected virtual. Accessing public methods is easy:

public class Class
{
  public virtual void Foo()
  {
  }
}

public class ClassTest
{
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<Class>();

    var config = ProxyManager.GetAction(proxy.Foo);
    ...
  }
}

But protected methods aren't as easy to access. This is were Lookup* calls are needed.

public class Class
{
  protected virtual void Foo()
  {
  }
}

public class ClassTest
{
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<Class>();

    var config = ProxyManager.LookupAction(proxy, "Foo");
    ...
  }
}

Most of my examples thus far have been really easy functions. Let's show one that is a bit more complex.

public class Class
{
  public virtual Func<Func<int>> WeirdFoo(int? nullable, int[] array, List<int> templated, List<int?>[] all, params int[] parms)
  {
    throw new NotImplementedException();
  }
}

public class ClassTest
{
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<DifficultParameter>();

    var weirdFooConfig =
        ProxyManager.GetFunc<int?, int[], List<int>, List<int?>[], int[], Func<Func<int>>>(proxy.WeirdFoo);

    weirdFooConfig.IsReal = true;

    TestUtils.AssertExceptionThrown<NotImplementedException>(() =>
    {
      proxy.WeirdFoo(null, null, null, null);
    });
  }
}

And what if the WeirdFoo was protected? You just use the Lookup*:

    var weirdFooConfig =
        ProxyManager.LookupFunc<int?, int[], List<int>, List<int?>[], int[], Func<Func<int>>>(proxy, "WeirdFoo");

Normally, if you want to invoke a protected method in a class you have to inherit from the class and create a public accessor for your test. Once you have the configuration for a proxy method (as shown above) you can invoke it as well:

    int? nullableInt = null;
    int[] intArray = null;
    List<int> intList = null;
    List<int?> nullableIntList = null;
    int[] anotherIntArray = null;
    Func<Func<int>> func = null;
    weirdFooConfig.Invoke(nullableInt, intArray, intList, nullableIntList, anotherIntArray, func);

Ref and Out Parameters

Building proxy support for ref and out parameters took me 3 days. The end result of code is extremely simple, but figuring out how to accomplish it was the hardest part. Many things in .NET Reflection are not well documented and very few people know how to navigate the many classes available in the System.Reflection namespace. After the ~90 hours I've now spent on BeanProxy I would consider myself 'aware' of what is available in .NET Reflection and how to accomplish many of those things, but I'm still learning new things all the time.

To reiterate, functions of type Action and Func with 0 to 5 parameters are supported in a very simple API with GetAction/GetFunc methods, as well as Lookup* for protected methods. To support ref and out in as simple and usable API would require me to expand the Get/Lookup calls from the current 24 variations to 2928 variations. It would be quite tedious. Additionally, the 2904 variations to be added could not use polymorphism because ref and out cannot be specified with templates. That would make for a huge number of ugly named functions. Considering that polymorphism could not be used, one might say it would not be a "simple and usable" solution. Therefore, we may conclude that such a solution does not exist. I felt the use of ref and out are so edge case that requiring a less intuitive API to configure ref and out parameters would be acceptable. Feel free to send me feedback if you feel otherwise.

Enough chit chat, here is an example using a function with both ref and out.
public class Class
{
  public virtual int Foo(ref int rint, out int oint)
  {
    oint = rint;
    return rint;
  }
}

public class ClassTest
{
  public delegate int FooType(ref int rint, out int oint);
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<Class>();

    int fakeOut = 4;
    int fakeReturn = 7;

    var fooConfig = ProxyManager.GetMethodConfig((FooType)proxy.Foo);
    fooConfig.IsReal = false;
    fooConfig.SetOutParameter(1, fakeOut);
    fooConfig.Callback = () => { return fakeReturn; };

    int rint = 10;
    int oint;

    var result = proxy.Foo(ref rint, out oint);

    Assert.AreEqual(fakeReturn, result, "return");
    Assert.AreEqual(4, oint, "oint");
    Assert.AreEqual(10, rint, "rint");
  }
}
As of this writing, ref's cannot be written to via SetOutParameter. Thus, there is actually no way to write a value to a ref inside the BeanProxy framework. This would be pretty easy to change so that ref's are given a value (default is not set), but some users have felt that an unhandled ref turning null would complicate otherwise simple tests. To dynamically give ref's a value only if a value was prepared in the configuration would require more work. Because there hasn't been any requests for ref value write support, I haven't added it. Again, if anyone using this library has such a need, I would be happy to discuss the functionality you would desire (and well as the proposed syntax).

Note that the use of GetMethodConfig in the above example also demonstrates how you could configure a method with more than 5 parameters. Even though you may have a function that is not using ref or out, it would not be accessible using the built in accessors if it has more than 5 parameters. Just as shown above, you can use GetMethodConfig using a delegate type which defines the long method signature.
public class LongSigsClass
{
  public virtual int Foo(int a, int b, int c, int d, int e, int f, int g, int h, int i)
  {
    return 1;
  }
}

public class LongSigsClassTest
{
  public delegate int FooType(int a, int b, int c, int d, int e, int f, int g, int h, int i);
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<LongSigsClass>();

    var config = ProxyManager.GetMethodConfig((FooType)proxy.Foo);
    ...
  }
}

Configuring Properties

Configuring properties is not as fluid as configuring methods. C# doesn't allow accessing the get/set methods of a property without reflection. Simply using the name of the property alone will invoke it. For this reason, in order to require the tester to use as few literal strings as possible (a major goal of BeanProxy), GetProp<> has a slightly different feel. Consider the following:
public class Class
{
  public virtual int Age { get; set; }
}

public class ClassTest
{
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<Class>();

    var getAgeConfig = ProxyManager.GetProp<int>(proxy, () => { return proxy.Age; });
    var setAgeConfig = ProxyManager.GetProp<int>(proxy, (v) => { proxy.Age = v; });

    bool ageSet = false;
    getAgeConfig.Return = 15;
    setAgeConfig.Callback = (v) => { ageSet = true; };

    proxy.Age = 77;
    Assert.AreEqual(15, proxy.Age, "get_Age");
    Assert.IsTrue(ageSet, "set_Age");
  }
}
Keep in mind that for a property to be configurable, it must be virtual. This requirement is no different for methods. When calling for a MethodConfig on a non-virtual method, a NotImplementedException will be thrown.

Using Callbacks

I find that using the Callback property can make for very simple and short tests. In the following example I'm trying to test BuildDocument and ignore what code may actually be in any other function it may call.

public class Class
{
  public virtual Stream BuildDocument(string text, byte[] imageData)
  {
    var package = BuildEmptyPackage();
    var image = CreateImage(imageData);

    WriteText(package, text);
    WriteImage(package, image);

    return GetPackageStream(package);
  }

  public virtual Package BuildEmptyPackage()
  {
    throw new NotImplementedException();
  }

  public virtual Image CreateImage(byte[] imageData)
  {
    throw new NotImplementedException();
  }

  public virtual void WriteText(Package pkg, string text)
  {
    throw new NotImplementedException();
  }

  public virtual void WriteImage(Package pkg, Image img)
  {
    throw new NotImplementedException();
  }

  public virtual Stream GetPackageStream(Package pkg)
  {
    throw new NotImplementedException();
  }
}

public class ClassTest
{
  public void Test()
  {
    var proxy = ProxyManager.CreateProxy<PackageClass>();

    ProxyManager.GetFunc<string, byte[], Stream>(proxy.BuildDocument).IsReal = true;

    string text = "text";
    byte[] imageData = new byte[] { 0x10 };

    var package = ProxyManager.CreateProxy<Package>(FileAccess.ReadWrite);

    var image = new Bitmap(1, 1);

    ProxyManager.GetFunc<Package>(proxy.BuildEmptyPackage).Return = package;
    ProxyManager.GetFunc<byte[], Image>(proxy.CreateImage).Callback = (data) =>
    {
      Assert.AreEqual(imageData, data, "image data");
      return image;
    };

    ProxyManager.GetAction<Package, string>(proxy.WriteText).Callback = (pkg, txt) =>
    {
      Assert.AreEqual(package, pkg, "package");
      Assert.AreEqual(text, txt, "text");
    };

    ProxyManager.GetAction<Package, Image>(proxy.WriteImage).Callback = (pkg, img) =>
    {
      Assert.AreEqual(package, pkg, "package");
      Assert.AreEqual(image, img, "image");
    };

    var stream = new MemoryStream();
    ProxyManager.GetFunc<Package, Stream>(proxy.GetPackageStream).Return = stream;

    var result = proxy.BuildDocument(text, imageData);

    Assert.AreEqual(stream, result, "result");
  }
}

You might notice that I didn't proxy Image. That's because Image has internal-only constructors. Some things in proxy building cannot be determined until build time. This includes:
  1. If a function is virtual
  2. If a Proxy Type is sealed, static, or internal
  3. If a specified constructor exists (see the following)

You might also have noticed that my Package manager constructor call was given a parameter. When you are creating a proxy and you want the proxy creation to use a non-default constructor, pass the constructor arguments to ProxyManager.CreateProxy<>(). If you give arguments of types that the assembly.CreateInstance can't associate with a specific constructor, an exception is thrown. In the case of a Package, there is no default constructor and the proxy would otherwise fail to build.

Last edited Apr 30, 2010 at 6:10 AM by payonel, version 3

Comments

No comments yet.