testfx icon indicating copy to clipboard operation
testfx copied to clipboard

ITestDataSource.GetData() could be easier to use with custom objects

Open Frannsoft opened this issue 6 years ago • 4 comments

Implementing ITestDataSource.GetData() currently requires an IEnumerable<object[]> to be returned. This is useful when wanting to return sets of primitives, but a bit tedious when the consumer wants to return more complex objects if I'm using this correctly.

Example:

        [TestMethod]
        [CarTestData]
        public void ATest(Car carUnderTest)
        {
            //perform test.
        }

        public class Car
        {
            public string Model { get; set; }
            public int Year { get; set; }
        }

The way I've done this for now is by implementing ITestDataSource.GetData() like so:

        //config for the 'CarTestData' attribute:
        class CarTestDataAttribute : Attribute, ITestDataSource
        {
            public IEnumerable<object[]> GetData(MethodInfo methodInfo)
            {
                return new List<object[]>
                {
                    new object[]{new Car { Model = "Ford", Year = 1990}},
                    new object[]{new Car {Model = "Nissan", Year = 2000} }
                };
            }
        }

Is there a better way I can do this? Or would it be possible to have a variant of ITestDataSource that returns IEnumerable<object> (or maybe IEnumerable<T>) instead of IEnumerable<object[]>?

I tried using DynamicDataAttribute to accomplish this as well and ran into a similar 'issue' of IEnumerable<object[]> as a required return value.

Environment

  • Windows 7
  • Visual Studio 2015
  • MSTest.Framework = 1.20-beta

Thanks for your time.

AB#1408171

Frannsoft avatar Aug 18 '17 13:08 Frannsoft

@Frannsoft: That is a fair ask. Tagging @harshjain2 to get his thoughts on this.

AbhitejJohn avatar Aug 23 '17 05:08 AbhitejJohn

Strange that ITestDataSource wasn't more like ITestDataSource<T>

aaronhudon avatar Mar 22 '18 23:03 aaronhudon

@harshjain2 Is there any traction on this? Seems like a fairly obvious functional gap for unit testing.

Justin-Lloyd avatar Nov 23 '18 17:11 Justin-Lloyd

I have used the following snippet

// see https://github.com/microsoft/testfx/blob/master/src/TestFramework/MSTest.Core/Attributes/DataRowAttribute.cs
public abstract class TestDataSourceAttribute : Attribute, ITestDataSource
{
  /// <summary>Gets Or sets display name in test results for customization.</summary>
  public string DisplayName { get; set; }
  public abstract IEnumerable<object[]> GetData(MethodInfo methodInfo);
  public virtual string GetDisplayName(MethodInfo methodInfo, object[] data)
  {
    if (!string.IsNullOrWhiteSpace(DisplayName))
    {
      return DisplayName;
    }
    else
    {
      return $"{methodInfo.Name} ({string.Join(",", data.AsEnumerable())})";
    }
  }
}

It is not possible to replace IEnumerable<object[]> by IEnumerable<T> because attributes must not be generic :/ So you would have to separate the attribute from the ITestDataSource implementation like so:

// see https://github.com/xunit/xunit/blob/main/src/xunit.v3.core/ClassDataAttribute.cs
public class ClassDataAttribute : TestDataSourceAttribute
{
  public Type Class { get; }
  /// <param name="class">The class that provides the data.</param>
  public ClassData(Type @class)
  {
    Class = @class;
  }
  public override IEnumerable<object[]> GetData(MethodInfo methodInfo)
  {
    if (Activator.CreateInstance(Class) is ITestDataSource dataSource)
    {
      return dataSource.GetData(methodInfo);
    }
    else
    {
      throw new Exception($"{Class.FullName} must implement IEnumerable<object[]> to be used as ClassData.");
    }
  }
}

Usage:

[DataTestMethod]
[ClassData(typeof(CarTestData))]
public void ATest(Car carUnderTest)
{
  // perform test.
}

hartmair avatar Jul 23 '20 04:07 hartmair