Skip to main content

Make sure your dynamic proxy is on the same page with your interface

In my previous post I was talking about how to create dynamic proxy using Castle DynamicProxy. Now, I want to shed some light on how to make sure that your proxy class complies with your interface, using special unit-tests.

First of all, let's take a step back and recall our proxy class and an interface:
public interface IUserService
{
    AuthenticatedUser Authenticate(string login, string password);

    void ChangeUserOffice(int userId, int officeId);
}

public class ClientUserService : Proxy<IUserService>
{
    public AuthenticatedUser Authenticate(string login, string password)
    {
        // do something
        return new AuthenticatedUser();
    }
}

The most obvious tests we can have for ClientUserService are those that show that it's working as expected:
[TestFixture]
public class InvocationTests
{
    private IUserService _service;

    [OneTimeSetUp]
    public void Setup()
    {
        var proxy = new ClientUserService();
        this._service = proxy.Interface;
    }

    [Test]
    public void Implemented_method_works_fine()
    {
        var result = this._service.Authenticate("test", "test");
        Assert.IsNotNull(result);
    }

    [Test]
    public void Not_implemented_method_throws_exception()
    {
        const int userId = 1;
        const int officeId = 2;
        Assert.Throws<NotImplementedException>(
            () => this._service.ChangeUserOffice(userId, officeId));
    }
}
As expected - tests above working just fine. However, they do not provide much value for us. Why? If we have just one interface with two methods, these tests are totally appropriate. But what if we have 5 interfaces (with a bunch of methods each) to proxy? How much effort we will need to support tests for each method in every interface we have? Let's create something smarter.

My plan is to create some comparison tests, using reflection. I assume that public proxy methods should have corresponding declarations defined in the interface, conforming to following rules:

  1. Methods match by name.
  2. Methods match by parameters.
  3. Methods match by returning type.
I will implement these rules as separate test-cases. But before, I need to create essential helper methods:
private static IEnumerable<MethodInfo> GetTypeMethods<T>()
{
    return typeof(T).GetMethods(
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
}

private static bool MethodsMatchByParameters(MethodInfo first, MethodInfo second)
{
    var firstParams = first.GetParameters().OrderBy(x => x.Position).ToArray();
    var secondParams = second.GetParameters().OrderBy(x => x.Position).ToArray();
    if (firstParams.Length != secondParams.Length)
    {
        return false;
    }

    var zippedParams =
        firstParams.Zip(secondParams, (f, s) => new {First = f, Second = s});
    return zippedParams.All(x => x.First.ParameterType == x.Second.ParameterType);
}
First one returns all public methods defined by the type. The second will tell if two methods possess same parameter lists.

First rule from the list is the most simple to implement:
[Test]
public void Proxy_has_methods_with_correct_names()
{
    var contractMethodNames =
        GetTypeMethods<IUserService>().Select(x => x.Name).ToArray();
    var proxyMethodNames =
        GetTypeMethods<ClientUserService>().Select(x => x.Name);
    var notContractProxyMethods =
        proxyMethodNames.Where(x => !contractMethodNames.Contains(x)).ToArray();
    Assert.IsTrue(
        notContractProxyMethods.Length == 0,
        $"Following methods do not belong to an interface: {string.Join(",", notContractProxyMethods)}");
}
Then goes "match by parameters" case:
[Test]
public void Proxy_methods_have_correct_parameters()
{
    var contractMethods = GetTypeMethods<IUserService>();
    var proxyMethods = GetTypeMethods<ClientUserService>();
    var proxyMethodToContractMethodsMap =
        proxyMethods.ToDictionary(
            x => x,
            x => contractMethods.Where(m => x.Name == m.Name).ToList());
    var proxyMethodsWithNotMatchingParams =
        proxyMethodToContractMethodsMap
            .Where(p => p.Value.All(x => !MethodsMatchByParameters(p.Key, x)))
            .Select(p => p.Key.Name)
            .ToArray();
     Assert.IsTrue(
         proxyMethodsWithNotMatchingParams.Length == 0,
         $"Following methods have incorrect list of parameters: {string.Join(",", proxyMethodsWithNotMatchingParams)}");
}
And finally, matching returning types:
[Test]
public void Proxy_methods_have_correct_return_types()
{
    var contractMethods = GetTypeMethods<IUserService>();
    var proxyMethods = GetTypeMethods<ClientUserService>();
    var proxyMethodToContractMethodsMap =
        proxyMethods.ToDictionary(
            x => x,
            x => contractMethods.Where(m => x.Name == m.Name).ToList());
    var proxyMethodsWithNotMatchingReturnTypes =
        proxyMethodToContractMethodsMap
            .SelectMany(
                 p => p.Value.Where(x => MethodsMatchByParameters(p.Key, x))
                          .Select(x => new {First = p.Key, Second = x}))
            .Where(x => x.First.ReturnType != x.Second.ReturnType)
            .Select(x => x.First.Name)
            .ToArray();
    Assert.IsTrue(
        proxyMethodsWithNotMatchingReturnTypes.Length == 0,
        $"Following methods have incorrect return types: {string.Join(",", proxyMethodsWithNotMatchingReturnTypes)}");
}

Here we go. All three test-cases can be checked easily for validity.
What further can be done to these tests? In case you have more than one proxy, you can make generic testfixture and parameterize it with as many proxy types as you wish, thus saving yourself time on writing code.

UPD: Code for this and previous article is on GitHub.

Comments

Popular posts from this blog