ryanyuyu
ryanyuyu

Reputation: 6486

Unit Test throws TargetParameterCountException when using params array

I'm setting up unit tests using Microsoft.VisualStudio.TestTools.UnitTesting test attributes. I'm trying to pass a different number of elements into a params array in my test method, as described in this answer.

Here's the relevant code in my [TestClass]:

[TestClass]
public class ExtensionsTests
{
    [TestMethod]
    [DataRow(1, "")]
    [DataRow(1, "stuff")]
    [DataRow(1, "asdf", "ASDF", "asDF", "ASdF")]
    [DataRow(3, "asdf", "ASDF", "1234", "d1sf5d1f")]
    [DataRow(2, "asdf", "ASDF", "ásdf", "ÁSdF")] //a and á (accent) are different characters 
    public void DbDistinct_ShouldBeCaseInsensitive(int expectedCount, params string[] strs)
    {
        var distinctStuff = strs.DbDistinct();
        Assert.AreEqual(expectedCount, distinctStuff.Count());
    }
}

When I run the unit test, I get this exception before it even make it into the body of the test:

Test method ExtensionsTests.DbDistinct_ShouldBeCaseInsensitive threw exception: System.Reflection.TargetParameterCountException: Parameter count mismatch. at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.ThreadOperations.ExecuteWithAbortSafety(Action action)

Interestingly, when I look at the error for a test case where there is only a single string to be passed into the params array (like the [DataRow(1, "stuff")] test case), I get a different error:

Test method ExtensionsTests.DbDistinct_ShouldBeCaseInsensitive threw exception: System.ArgumentException: Object of type 'System.String' cannot be converted to type 'System.String[]'. at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast) at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr) at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig) at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.ThreadOperations.ExecuteWithAbortSafety(Action action)

It looks like the DataRow is ignoring the params part of the test method. How can I specify a variable number of test elements to be passed into my test method? My test project is .NET Framework 4.8 (C# 7.3). I get the same error in both Rider and VS2022, so I don't think this is IDE-related.

Upvotes: 2

Views: 345

Answers (2)

ryanyuyu
ryanyuyu

Reputation: 6486

Cause of the Exception

I think figured out why I ran into the Parameter mismatch or the invalid casting issues. The best guess I found based on a linked article on another Stack Overflow answer:

The DataRow attribute has a constructor that doesn't play well with arrays because the constructor itself accepts params Object[]. This means that if you have an array whose type could fill the role of Object[], the compiler prefers matching your array directly instead of expanding it as part of a params array.

The suggested solution to explicitly wrap things properly (with a collection initializer) is not compatible with Attributes because everything in an Attribute must be a compile-time constant (and therefore initializers and new don't work).

My Solution: use [DynamicData]

I ended up side-stepping this issue by using a DynamicData test cases. I defined a static property that generates my test cases. Each test case is defined as an object[], whose elements are injected as parameters in my test method (in order). I made each test case be an expected value (int) and the test data (string[]):

public static IEnumerable<object[]> CaseInsensitiveTestCases
{
    get
    {
        return new[]
        {
            new object[] { 1, new string[] { "" } },
            new object[] { 1, new string[] { "stuff" } },
            new object[] { 1, new string[] { "asdf", "ASDF", "asDF", "ASdF" } },
            new object[] { 3, new string[] { "asdf", "ASDF", "1234", "d1sf5d1f" } },
            new object[] { 2, new string[] { "asdf", "ASDF", "ásdf", "ÁSdF" } }, //a and á (accent) are different characters 
        };
    }
}

Then I just used [DynamicData] attribute on my unit test method:

[TestMethod]
[DynamicData(nameof(CaseInsensitiveTestCases))]
public void DbDistinct_ShouldBeCaseInsensitive(int expectedCount, string[] strs)
{
    var distinctStuff = strs.DbDistinct();
    Assert.AreEqual(expectedCount, distinctStuff.Count());
}

Upvotes: 1

quain
quain

Reputation: 984

Have you tried to change test method parameters to int, string, and then separate params collection? It's a workaround but should do the trick. Off course you will need to compose input for method being tested.

Upvotes: -1

Related Questions