Cannot provide 2 arrays to DataRow
See original GitHub issueDescription
DataRow does not allow providing 2 array parameters…
Steps to reproduce
Use [DataRow(new[] { "a" }, new[] {"b"} )]
in a test.
Expected behavior
I can provide any reasonable number of parameters in the same form. Possibly there are constructor overloads that take 1 - 7 parameters for DataRow, and DataRow is not sealed so I can add more.
Actual behavior
You get error:
CS0182 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.
This is confusing because we do provide types that fit this criteria, and because using three arrays works just fine.
[DataRow(new[] { "a" }, new[] { "b" }, new[] { "c" })]
In reality you are running into the implementation detail of DataRow which uses params and that makes array mismatch between the inferred string[]
and the destination object[]
types.
You can work around the issue by wrapping your parameters into additional array, or by subclassing DataRow:
[TestClass]
public class UnitTest1
{
[TestMethod]
// [DataRow(new[] { "a" }, new[] { "b" })] // Does not compile
[DataRow(new[] { "a" }, new object[] { new[] { "b" } })]
[DataRow2(new[] { "c" }, new[] { "d" })]
public void TestMethod1(string[] _, string[] __)
{
}
[TestMethod]
[DataRow(new[] { "a" }, new[] { "b" }, new[] { "c" })]
[DataRow2(new[] { "d" }, new[] { "e" }, new[] { "f" })]
public void TestMethod2(string[] _, string[] __, string[] ___)
{
}
}
public class DataRow2Attribute : DataRowAttribute
{
public DataRow2Attribute(object data1) : base(data1) { }
public DataRow2Attribute(object data1, object data2) : base(data1, new[] { data2 }) { }
public DataRow2Attribute(object data1, object data2, object data3) : base(data1, new[] { data2, data3 }) { }
public DataRow2Attribute(object data1, object data2, object data3, object data4) : base(data1, new[] { data2, data3, data4 }) { }
}
This still does not fix the issue fully, because the name of the method is translated to TestMethod2 (System.String[],System.String[],System.String[])
, which makes only one of the row to run per method, not both.
Get display name is not overridable, so unless I want to implement the whole attribute myself I don’t have an easy way to override it.
Possible implementation of GetDisplayName would look into all the arrays and expand them.
public string GetDisplayName(MethodInfo methodInfo, object[] data)
{
if (!string.IsNullOrWhiteSpace(DisplayName))
{
return DisplayName!;
}
var stringBuilder = new StringBuilder(methodInfo.Name);
stringBuilder.Append('(');
// Add params.
var first = true;
foreach (var d in data)
{
if (!first)
{
stringBuilder.Append(", ");
}
first = false;
if (d.GetType().IsArray)
{
stringBuilder.Append("@(");
var first2 = true;
foreach (var v in (IEnumerable)d)
{
if (!first2)
{
stringBuilder.Append(", ");
}
first2 = false;
stringBuilder.Append(v?.ToString() ?? "<null>");
}
stringBuilder.Append(')');
}
else
{
stringBuilder.Append(d);
}
}
stringBuilder.Append(')');
return stringBuilder.ToString();
}
Environment
Please share additional details about the test environment. Windows,
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
❗ Latest 2.3.0-preview has a regression, where it only runs the last of the tests, even when the names are unique.
Issue Analytics
- State:
- Created a year ago
- Comments:5 (5 by maintainers)
Top GitHub Comments
I understand although for
DataRow
, the elements are just inline so that’s super easy to have access to them. For example:[DataRow(new[] { "a" }, new[] { "b" }, DisplayName= "MyMethod ([a], [b]")]
.I still think it’s easy enough to make
GetDisplayName
virtual and allow for custom behavior so I have created a new ticket to track this part: https://github.com/microsoft/testfx/issues/1336Not sure if we are talking about the same problem. I was saying that I see weird behavior in version 2.3.0 and I think that it is already captured in issue #1016.
DisplayName is settable, but that does not solve the problem imho. To generate DisplayName I need to get the input data (method and params) and I get those in GetDisplayName.
I also most likely want to rely on the
base
implementation of GetDisplayName, and only add some additional info, not re-implement the whole method from scratch or hide it. So imho GetDisplayName should be virtual.