How to supply two arrays as DataRow parameters? - c#

I am trying to write a unit test that will compare two arrays. I have defined the unit test as so:
[DataTestMethod]
[DataRow(
new[] { "COM3", "COM1", "COM2" },
new[] { "COM1", "COM2", "COM3" }
)]
...
public void TestCOMPortSorting(string[] unorderedPorts, string[] expectedOrderedPorts)
However, my IDE throws the following error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
I have tried using external variables, defining the arrays as new string[], creating a single array with these arrays, all with no luck.
How can I use these two arrays as parameters for my unit test?

For such complex data, change to using the DynamicData attribute
This attribute allows to get the values of the parameters from a method or a property. The method or the property must return an IEnumerable<object[]>. Each row corresponds to the values of a test.
[DataTestMethod]
[DynamicData(nameof(TestDataMethod), DynamicDataSourceType.Method)]
public void TestCOMPortSorting(string[] unorderedPorts, string[] expectedOrderedPorts) {
//...
}
static IEnumerable<object[]> TestDataMethod() {
return new[] {
new []{ new[] { "COM3", "COM1", "COM2" }, new[] { "COM1", "COM2", "COM3" } } //a data row
};
}
Reference MSTest v2: Data tests

DataRowAttribute can handle arrays of simple types. The issue here is that DataRowAttribute has the following overload for more than one parameters: DataRowAttribute(object data1, params object[] moreData).
In your expression, I think C# takes the second string array as the object[] moreData and it doesn't like it. If you specify the params object[] argument explicitly it will take the second string array as expected.
[DataTestMethod]
[DataRow(
new[] { "COM3", "COM1", "COM2" },
new object[] { new[] { "COM1", "COM2", "COM3" } }
)]
...
public void TestCOMPortSorting(string[] unorderedPorts, string[] expectedOrderedPorts)
Note that if you have any other 3rd argument it works without the workaround.

Related

C# using multi dim array as input for DataRow (MSTest)

I'm currently building a Test Project and I need to pass several arguments to the test function. Because I need to call the test function with different parameter sets I decided to use ms-test with the [DataTestMethod].
Now I need to pass jagged arrays as Parameters to the function.
But I don't get it to work.
The call for TestMethod1 is working.
The call for TestMethod2 is not working as it is not successfully compiling.
CS0182:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UnitTestProject2
{
[TestClass]
public class UnitTest1
{
[DataTestMethod]
[DataRow(new int[] { })]
public void TestMethod1(int[] values)
{
}
[DataTestMethod]
[DataRow(new int [][] { } )]
public void TestMethod2(int[][] values)
{
}
}
}
Does anyone has any suggestion to get this working?
Sadly I need to use some kind of two dimension data type because I need to pass information about two loops inside of the test function. I can't use the params keyword because I need two of this jagged arrays in the test function.
Regards
White
You cannot use jagged array as a parameter in an attribute, because you cannot declare it as a const. More explanation in here: Const multi-dimensional array initialization
For your purpose I would use DynamicDataAttribute:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
namespace UnitTestProject
{
[TestClass]
public class TestClass
{
static IEnumerable<int[][][]> GetJaggedArray
{
get
{
return new List<int[][][]>
{
new int[][][]
{
new int [][]
{
new int[] { 1 },
new int[] { 2, 3, 4 },
new int[] { 5, 6 }
}
}
};
}
}
[TestMethod]
[DynamicData(nameof(GetJaggedArray))]
public void Test1(int[][] jaggedArray)
{
Assert.AreEqual(1, jaggedArray[0][0]);
Assert.AreEqual(2, jaggedArray[1][0]);
Assert.AreEqual(3, jaggedArray[1][1]);
Assert.AreEqual(4, jaggedArray[1][2]);
Assert.AreEqual(5, jaggedArray[2][0]);
Assert.AreEqual(6, jaggedArray[2][1]);
}
}
}
I know that syntax IEnumerable<int[][][]> is not pleasant for eyes, but since DynamicDataAttribute.GetData(MethodInfo) method is returning IEnumerable<object[]>, and your object is int[][], this is what you get.

Pass in array of certain size to test with xunit

I need help with writing unit test with xunit. I am trying to test a validation where array length cannot be greater than 20MB
[Theory]
[InlineData((arrayGreaterThan20MB())]
public void Fail_when_documentSize_is_greater(byte[] documentSize)
{
_getAccountFileViewModel.Content = documentSize;
}
Private Function
private static byte[] arrayGreaterThan20MB()
{
byte[] arr = new byte[5000000000];
return arr;
}
I am not sure what is the best way to test this. I am getting a error when I am trying to pass function in inline data.
Error "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type"
Just declare the array within the test itself, instead of trying to pass via inline data attribute
[Theory]
public void Fail_when_documentSize_is_greater() {
byte[] overSizedDocument = new byte[5000000000];
_getAccountFileViewModel.Content = overSizedDocument;
//...
}
You can not use the result of a method call as parameter of an attribute. That's what the error is telling you. You can only pass constants or literals. For example like this:
[Theory]
[InlineData("a", "b")]
public void InlineDataTest(string first, string second)
{
Assert.Equal("a", first);
Assert.Equal("b", second);
}
XUnit has some other attributes, that can help you here. E.g. there is the MemberData attribute, that allows you to specify a method name of a method that provides the test data. Note that it returns an IEnumerable of object array. Each object array will be used to call the test method once. The content of the object array are the parameters. For example:
[Theory]
[MemberData(nameof(DataGeneratorMethod))]
public void MemberDataTest(string first, string second)
{
Assert.Equal("a", first);
Assert.Equal("b", second);
}
public static IEnumerable<object[]> DataGeneratorMethod()
{
var result = new List<object[]>(); // each item of this list will cause a call to your test method
result.Add(new object[] {"a", "b"}); // "a" and "b" are parameters for one test method call
return result;
// or
// yield return new object[] {"a", "b"};
}
In the case you mentioned above, the simplest way would be just to call your method that creates the test data within your test method.
[Theory]
public void Fail_when_documentSize_is_greater()
{
_getAccountFileViewModel.Content = arrayGreaterThan20MB();
}
There is another attribute called ClassData that can use a data generator class.
More detailed information can be found on this blog post

c# an attribute argument must be a constant expression

Why does my String array below give me an Error, arent they all strings???
"An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type"
[Test]
[TestCase(new string[]{"01","02","03","04","05","06","07","08","09","10"},TestName="Checking10WOs")]
public void Test(String[] recordNumber)
{
//something..
}
This does not answer to the title of question, however it solves your specific problem.
You might want to use TestCaseSource, it allows you to pass multiple test case scenarios into the same testing mechanism, and you can use it as complex structures as you like.
[Test]
[TestCaseSource("TestCaseSourceData")]
public void Test(String[] recordNumber, string testName)
{
//something..
}
public static IEnumerable<TestCaseData> TestCaseSourceData()
{
yield return new TestCaseData(new string[] {"01", "02", "03", "04", "05", "06", "07", "08", "09", "10"}, "Checking10WOs");
}
It will figure out that the first parameter is recordNumber and the second is testName
see the screenshot below.
Hope this saves you some time.
The strings are all constant but the array they are in is not. Try this instead:
[Test]
[TestCase("01","02","03","04","05","06","07","08","09","10", TestName="Checking10WOs")]
public void Test(String recordNumber)
{
//something..
}
This works because TestCaseAttribute accepts its cases as a params list.

Alternating params

Is there a kind of alternating params for method parameters?
I like the keyword params. But sometimes I need two parameters to be params.
I want to call a method like so:
Method(1, "a", 2, "b", 3, "c")
where 1, 2 and 3 are keys and "a", "b" and "c" are assigned values.
If I try to define the method parameters I would intuitively try to use params for two parameters like so:
void Method(params int[] i, string[] s)
Compiler would add every parameter at odd positions to the first parameter and every parameter at even positions to the second parameter.
But (as you know) params is only possible for last parameter.
Of course I could create a parameter class (e.g. KeyValue) and use it so:
Method(new[] {new KeyValue(1, "a"), new KeyValue(2, "b"), new KeyValue(3, "c")})
But that is too much code imo.
Is there any shorter notation?
Edit: Just now I found a good answer to another question: It suggests to inherit from List and to overload the Add method so that the new List can be initialized by this way:
new KeyValueList<int, string>{{ 1, "a" }, { 2, "b" }, { 3, "c" }}
Method definition would be:
void Method(KeyValueList<int, string> list)
Call would be:
Method(new KeyValueList<int, string>{{ 1, "a" }, { 2, "b" }, { 3, "c" }})
There is no "alternating params" notation as you described.
You can only have one params parameter and it must be last - if you want to have different types as params parameters you can use object as the array type.
Consider passing in a list made of a custom type that retains the meaning of these items.
public class MyType
{
public int MyNum { get; set; }
public string MyStr { get; set; }
}
Method(List<MyType> myList);
You could do this via params object[] keysAndValues and sort it out yourself, but... its a bit icky, what with all the boxing/unboxing that would go on.
Just giving an updated answer for today's developers looking for a solution...
Old School Answer:
You could create a class or struct that includes the parameters of the type you need...
struct Struct {
public int Integer {get; set;}
public string Text {get; set;}
public Struct(int integer, string text) {
Integer = integer;
Text = text;
}
}
... and then define your function to accept it as the params array...
void Method(params Struct[] structs)
... and call it like...
Method(new Struct(1, "A"), new Struct(2, "B"), new Struct(3, "C"));
New School Answer:
Or, with the latest C#, you could simply use ValueTuples...
Method(params (int, string)[] valueTuples)
... and call it like...
Method((1, "A"), (2, "B"), (3, "C"))

How will be object[] params interpreted if they are passed into another procedure with object[] params?

If we have two procedures:
public void MyFirstParamsFunction(params object[] items)
{
//do something
MySecondParamsFunction(items, "test string", 31415)
}
public void MySecondParamsFunction(params object[] items)
{
//do something
}
How will second procedure interpret the items from first? As sole objects, or as one object, that is object[]?
object[] items will be interpreted as one object[] parameter.
This call:
MySecondParamsFunction(items, "test string", 31415);
Will expand to this:
MySecondParamsFunction(new object[] { items, "test string", 31415 });
So you'll have 3 items in your params array for the call. Your original items array will be crammed into the first item in the new array.
If you want to just have a flattened parameter list going into the second method, you can append the new items to the old array using something like this:
MySecondParamsFunction(
items.Concat(new object[] { "test string", 31415 }).ToArray());
Or maybe with a nicer extension method:
MySecondParamsFunction(items.Append("test string", 31415));
// ...
public static class ArrayExtensions {
public static T[] Append<T>(this T[] self, params T[] items) {
return self.Concat(items).ToArray();
}
}
If you check it yourself you would have noticed that it is seen as a single object.
So in MySecondParamsFunction the items parameter will have length of 3 in this case.
The params keyword is just a bit of syntactic sugar.
Calling MyFirstParamsFunction(1, 2, 3) is the same as MyFirstParamsFunction(new object[] { 1, 2, 3 }). The compiler injects the code to create the array behind your back.

Categories

Resources