xUnit.net, PropertyDataAttribute and derived classes

.NET, English posts, xUnit Comments (1)

Apparently do not work well together...

Consider the following:

	public class Foo
	{
		public int AddsNumbers(int x, int y) { return x + y; }
	}

	public class FooTest
	{
		public static IEnumerable<object[]> Expectations
		{
			get
			{
				yield return new object[] { 1, 2, 3 };
			}
		}

		[Theory]
		[PropertyData("Expectations")]
		public void Adds_ReturnsExpectedValues(int x, int y, int expected)
		{
			Assert.Equal(expected, factory().AddsNumbers(x, y));
		}
	}

All will work well - the method Adds_ReturnsExpectedValues will be called with the parameters taken from the Expectations property as expected. But what if we wanted to have this FooTest an abstract base class, and have basic tests there, while declaring the Expectations property in a derived class?

This scenario makes sense when you have a good class hierarchy; you can then have many basic tests done on the general level at the abstract class, and specialized tests on the derived classes, while still controlling the parameters passed to them from the derived classes.

But when you try that, you discover it doesn't work with xUnit.net. xUnit will only take the PropertyData on the declaring type, e.g. the abstract class, and completely ignore the derived classes, even if it is them who actually triggered the test. You immediately start thinking about writing a method to manually run those tests, but that's quite of an headache when you have repeating tests, data properties, etc.

Luckily, this is an easy fix. I just took the sources for PropertyDataAttribute from the xUnit.net project, and changed it to the following (only the changed method is shown) and now it all works (giving the type triggered the test a priority):

		public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
		{
			Type type = PropertyType ?? methodUnderTest.ReflectedType;
			PropertyInfo propInfo = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
			if (propInfo == null)
			{
				string typeName = type.FullName;
				if (methodUnderTest.DeclaringType != null)
				{
					propInfo = methodUnderTest.DeclaringType.GetProperty(propertyName,
					                                                     BindingFlags.Public | BindingFlags.Static |
					                                                     BindingFlags.FlattenHierarchy);
					typeName = "neither " + typeName + " nor " + methodUnderTest.DeclaringType.FullName;
				}

				if (propInfo == null)
					throw new ArgumentException(string.Format("Could not find public static property {0} on {1}", propertyName,
				                                          type.FullName));
			}

			object obj = propInfo.GetValue(null, null);
			if (obj == null)
				return null;

			IEnumerable<object[]> dataItems = obj as IEnumerable<object[]>;
			if (dataItems == null)
				throw new ArgumentException(string.Format("Property {0} on {1} did not return IEnumerable<object[]>", propertyName, type.FullName));

			return dataItems;
		}

Comments

  • Ruben Bartelink

    A new overload has been added to PropertyData which takes a Type recently (and some other things in that space - there may even be one that walks the type hierarchy, I can't recall exactlu). Not sure if that's on NuGet as yet though...

Comments are now closed