Robin Elvin
Robin Elvin

Reputation: 1255

How can I mock a property of a custom Attribute in order to test it?

I'm having trouble getting my head around how to test a custom Attribute where I need to mock an internal state property. The Attribute adds an extension method.

The Configuration property was a static property which works fine in practice but does not work when running parallel tests.

Some psuedo-code of what I have:

public class MyAttribute : Attribute
{
    // Store a complex object that cannot be passed into constructor
    private static IConfiguration _configuration;

    public static IConfiguration
    {
        get {
            if (_configuration == null)
                _configuration = new Configuration();
            return _configuration;
        }
        set {
            _configuration => value;
        }
    }

    public MyAttribute(string foo)
    {
        // foo is a simple type
    }
}

public static class MyExtensionClass
{
    public static Task<T> DoSomething<T>(this Delegate method, params object[] args)
    {
        DoSomethingWithConfiguration(MyAttribute.Configuration);

        // or if I make Configuration an instance property
        MethodInfo mi = method.GetMethodInfo();
        MyAttribute attr = mi.GetCustomAttribute<MyAttribute>();
        DoSomethingWithConfiguration(attr.Configuration);

        return ....
    }
}

An example test:

public class TestClass
{
    [MyAttribute(foo="bar")]
    public int Add(int x, int y)
    {
        return x + y;
    }

    delegate int AddDelegate(int x, int y);

    [Fact]
    public void TestSomething()
    {
        Delegate d = new AddDelegate(Add);
        Mock<IConfiguration> mockConfig = new Mock<IConfiguration>();
        mockConfig.Setup(.....); // set up mocked interface
        // ** Need to inject mock into Attribute class somehow

        // ** This doesn't work with parallel test runs **
        MyAttribute.Configuration = mockConfig.Object;
        var result = d.DoSomething<int>(1, 2);
    }
}

If I mock IConfiguration and set the static property then when my tests run in parallel the value is overwritten and tests fail randomly. I know this is the wrong way to do things. Specifically, the Configuration class indirectly connects to a server (by way of a factory method) hence the need to mock it. I thought I somehow needed to get hold of the MyAttribute instance before it does anything but I don't know how. I'm stuck as how to refactor this to make it testable.

Upvotes: 0

Views: 2132

Answers (1)

NightOwl888
NightOwl888

Reputation: 56849

when my tests run in parallel the value is overwritten and tests fail randomly.

This is happening because your IConfiguration instance is static. That means that any instance of MyAttribute will share the same state with every other instance and the value of _configuration will be set for every instance to the last value that has been set on any instance. Just like on the movie Highlander there can be only one.

private static IConfiguration _configuration;

[I want to] Store a complex object that cannot be passed into constructor

You can't. This is a limitation of attributes, because their purpose is to store metadata, which is compiled into the assembly.

They can only accept a limited number of types (reference):

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (§17.2).
  • Single-dimensional arrays of the above types.

A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.

So, what you are attempting to do is not possible.

Furthermore, as per Passive Attributes it is not very practical to make attributes that contain behavior.

The problem with this approach is that attribute instances are created by the run-time, so you can't use proper Dependency Injection (DI) patterns such as Constructor Injection. If an attribute defines behaviour (which many of the Web API attributes do), the most common attempt at writing loosely coupled code is to resort to a static Service Locator (an anti-pattern).

Without the dependency injection pattern, it is difficult to test the logic within classes.

I'm stuck as how to refactor this to make it testable.

Since you are trying to do something that is not even possible (that is, set an attribute value to a type it doesn't support), it is hard to give you any constructive advice.

The only thing I can suggest is don't use attributes for anything other than markers. You can decorate members with them and read their state using Reflection from other (testable) parts of your application to control the application's behavior, but don't put any behavior inside of attributes.

Upvotes: 2

Related Questions