I have a test class and I want write a test for one of my functions and I have to use HashSet in [Theory] inlineData but I can't use it.
[Theory]
[InlineData(new HashSet<string>() {"text1","text2"}, "str")]
public void BuildInvertedIndexTest(ISet<string> expectedDocContain, string searchingWord)
I wrote classData and memberData but It wasn't successful.
please guide me.
With MemberDataAttribute the solution could look like this:
public static IEnumerable<object[]> BuildInvertedIndexTestData()
{
yield return new object[] { new HashSet<string>() { "text1", "text2" }, "str" };
yield return new object[] { ... };
}
The usage would look like this:
[Theory, MemberData(nameof(BuildInvertedIndexTestData))]
public void BuildInvertedIndexTest(ISet<string> expectedDocContain, string searchingWord)
I haven't tested this solution so it might happen that you need to change the expectedDocContain parameter's type to HashSet<string>.
You cannot use HashSet<T> with [InlineData] as attributes only support simple types like string, int, bool, arrays etc.
You will need to use another way of parameterising the test, such as [MemberData] for ISet<T>/HashSet<T> which you can read more about in this blog post by Andrew Lock: https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdata/
Alternatively, you could use a string[] with [InlineData] and construct the HashSet<string> from that in the body of the test.
[Theory]
[InlineData(new string[] {"text1","text2"}, "six")]
public void BuildInvertedIndexTest(string[] expectedDocContain, string searchingWord)
{
var hashSet = new HashSet<string>(expectedDocContain);
// TODO Put the rest of your test here.
}
Related
I understand how to pass one array to xUnit: Pass array of string to xunit test method
However, I'm in a situation where I want to pass three arrays to my test method:
Type1[] ReturnValuesForMock;
Type2[] ExpectedTestResult;
Type3[] TestData
Is there any way to do this? Type1 is an enum, so I can use a compile-time constant new [] {enum} but that doesn't work for Type2 which needs a call to new(). I can then deal with Type3 as params.
I feel like this really ought to be possible but I'm not sure how...
I'm not sure if this is the best way, but I've worked out that I can use the [ClassData] attribute on my tests like this:
[Theory]
[ClassData(typeof(TestDataGenerator))]
public void Test1(Type1[] t1, Type2[] t2, Type3[] t3)
{
//MyTest
}
And then provide the data through a TestDataGenerator class:
private class TestDataGenerator:IEnumerable<object[]>
{
private static readonly List<object[]> _data = new()
{
new object[]{new Type1[] {...}, new Type2[]{...)}, new Type3[]{...}},
...
};
public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
My log4net object looks something like this (since I want to log my set as a json object)
_log.Debug(new JObject(new JProperty("Prop1", "Val1"),
new JProperty("Prop2", "Val2")).ToString());
For better code readability and easy adding of additional properties, I'd like to do something like this
Utility.WriteLog({"Prop1", "Val1"},
{"Prop2", "Val2"});
I am not sure how the WriteLog() method would look like other than that it should have a params argument considering that there will be a variable number of property/value pairs that need to be logged.
I have considered a dictionary (see below) and the WriteLog function would create a JsonObject with the property/value keys from the dictionary:
Utility.WriteLog(new Dictionary<string, string>()
{
{"Prop1", "Val1"},
{"Prop2", "Val2" }
});
Is this the best approach, or is there a more succinct alternative?
If you are using c# 7.0 or later, you could use the simplified tuple syntax to pass in a params array of name/value tuples for formatting via serialization and subsequent logging.
Since you have tagged your question log4net, you could define extension methods on ILog like so:
public static partial class LogExtensions
{
public static void DebugProperties(this ILog log, params (string Name, object Value) [] parameters)
// TODO: handle duplicate Name keys in some graceful manner.
=> log.Debug(JsonConvert.SerializeObject(parameters.ToDictionary(p => p.Name, p => p.Value), Formatting.Indented));
public static void InfoProperties(this ILog log, params (string Name, object Value) [] parameters)
=> log.Info(JsonConvert.SerializeObject(parameters.ToDictionary(p => p.Name, p => p.Value), Formatting.Indented));
}
And then call them like:
log.DebugProperties(("Prop1", "Val1"), ("Prop2", "Val2"));
log.InfoProperties(("Prop3", new SomeClass { SomeValue = "hello" }));
And get the output:
2021-01-21 21:12:36,215 DEBUG: {
"Prop1": "Val1",
"Prop2": "Val2"
}
2021-01-21 21:12:36,230 INFO : {
"Prop3": {
"SomeValue": "hello"
}
}
If you are using c# 9.0 or later, you could also add a logging method taking a Dictionary<string, object> and, when calling the method, use the abbreviated new () syntax which omits the type when already known:
public static partial class LogExtensions
{
public static void DebugDictionary(this ILog log, Dictionary<string, object> parameters)
=> log.Debug(JsonConvert.SerializeObject(parameters, Formatting.Indented));
}
And then later:
log.DebugDictionary(new () { {"Prop1", "Val1"}, {"Prop2", "Val2" } } );
Of course, if you prefer you could wrap your ILog log in a Utility class with methods whose inputs are like those of the extension methods above, but it isn't necessary if you use extension methods.
Demo fiddle here.
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
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.
I'm currently having the following method which requires params - I cannot change this methods definition:
public static DoStuff(params Parameter[] p){ /* ... */ }
I do not know how many parameters I'll have at runtime, so I want to pass an array or a list - I've thought about using Iterators like this:
List<Parameter> pList = new List<Parameter>();
pList.Add(new Parameter("#a", "aaaa!");
pList.Add(new Parameter("#b", "bbbb!");
pList.Add(new Parameter("#c", "cccc!");
DoStuff(Iterate(pList));
And here's the Iterate-Method:
public static IEnumerator<Parameter> Iterate(List<Parameter> pList)
{
foreach (Parameter p in pList)
yield return p;
}
Unfortunately, it does not work, it keeps telling me that it can't cast (generated type) to CommandParameter.
Any help (or different approaches) would be appreciated!
Edit: It appears, in an attempt to simplify my solution for the sake of making my question understandable I trivialized my problem.
In addition to this array, I'd like to also pass "normal" Parameters like this:
DoStuff(Iterate(pList), new Parameter("#d", "dddd!"));
I do this merely out of curiosity to see whether it works - but this does not work simply by casting my List to an Array and appending the next Parameter.
Thanks,
Dennis
You could do it like that:
DoStuff(pList.Concat(new[]{ new Parameter("#d", "dddd!"), new Parameter("#e", "eeee!"}).ToArray());
As far as I know, the params keyword requires an array as parameter type, and you could use DoStuff(pList.ToArray()); then.
The definition you specified though should then be
public static DoStuff(params Parameter[] p){ /* ... */ }
Or have I missed out on some new features?
Out of interest, why do you need to use the Iterate / yield method?
Is there a reason why this would not work?
static void Main(string[] args)
{
List<Parameter> pList = new List<Parameter>();
pList.Add(new Parameter("#a", "aaaa!"));
pList.Add(new Parameter("#b", "bbbb!"));
pList.Add(new Parameter("#c", "cccc!"));
DoStuff(pList.ToArray());
}
static void DoStuff(params Parameter[] parameters){
foreach(var p in parameters)
{
//do something.
}
}