问题
I'm pretty new to xUnit and here's what I'd like to achieve:
[Theory]
[InlineData((Config y) => y.Param1)]
[InlineData((Config y) => y.Param2)]
public void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
Basically, I have a GenericConfig object which contains Config and other kind of configurations, but I need to check that every single parameter is valid. Since they're all string, I wanted to simplify using [InlineData] attribute instead of writing N equals tests.
Unfortunately the error I'm getting is "Cannot convert lambda expression to type 'object[]' because it's not a delegate type", which is pretty much clear.
Do you have any idea on how to overcome this?
回答1:
In addition to the already posted answers. The test cases can be simplified by directly yielding the lambdas.
public class ConfigTestDataProvider
{
public static IEnumerable<object[]> TestCases
{
get
{
yield return new object [] { (Func<Config, object>)((x) => x.Param1) };
yield return new object [] { (Func<Config, object>)((x) => x.Param2) };
}
}
}
This test ConfigTestDataProvider
can then directly inject the lambdas.
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(Func<Config, object> func)
{
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
回答2:
Actually, I was able to find a solution which is a bit better than the one provided by Iqon (thank you!).
Apparently, the InlineData
attribute only supports primitive data types. If you need more complex types, you can use the MemberData
attribute to feed the unit test with data from a custom data provider.
Here's how I solved the problem:
public class ConfigTestCase
{
public static readonly IReadOnlyDictionary<string, Func<Config, string>> testCases = new Dictionary<string, Func<Config, string>>
{
{ nameof(Config.Param1), (Config x) => x.Param1 },
{ nameof(Config.Param2), (Config x) => x.Param2 }
}
.ToImmutableDictionary();
public static IEnumerable<object[]> TestCases
{
get
{
var items = new List<object[]>();
foreach (var item in testCases)
items.Add(new object[] { item.Key });
return items;
}
}
}
And here's the test method:
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(string currentField)
{
var func = ConfigTestCase.testCases.FirstOrDefault(x => x.Key == currentField).Value;
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
I could maybe come up with something a bit better or cleaner, but for now it works and the code is not duplicated.
回答3:
I have the problem the same to you, and I found the solution that using TheoryData
class and MemberData
attribute. Here is the example and I hope the code usefully:
public class FooServiceTest
{
private IFooService _fooService;
private Mock<IFooRepository> _fooRepository;
//dummy data expression
//first parameter is expression
//second parameter is expected
public static TheoryData<Expression<Func<Foo, bool>>, object> dataExpression = new TheoryData<Expression<Func<Foo, bool>>, object>()
{
{ (p) => p.FooName == "Helios", "Helios" },
{ (p) => p.FooDescription == "Helios" && p.FooId == 1, "Helios" },
{ (p) => p.FooId == 2, "Poseidon" },
};
//dummy data source
public static List<Foo> DataTest = new List<Foo>
{
new Foo() { FooId = 1, FooName = "Helios", FooDescription = "Helios Description" },
new Foo() { FooId = 2, FooName = "Poseidon", FooDescription = "Poseidon Description" },
};
//constructor
public FooServiceTest()
{
this._fooRepository = new Mock<IFooRepository>();
this._fooService = new FooService(this._fooRepository.Object);
}
[Theory]
[MemberData(nameof(dataExpression))]
public void Find_Test(Expression<Func<Foo, bool>> expression, object expected)
{
this._fooRepository.Setup(setup => setup.FindAsync(It.IsAny<Expression<Func<Foo, bool>>>()))
.ReturnsAsync(DataTest.Where(expression.Compile()));
var actual = this._fooService.FindAsync(expression).Result;
Assert.Equal(expected, actual.FooName);
}
}
回答4:
Oddly delegates are not objects, but Action
s or Func
s are. To do this, you have to cast the lambda to one of these types.
object o = (Func<Config, string>)((Config y) => y.Param1)
But doing this, your expression is not constant anymore. So this will prevent usage in an Attribute
.
There is no way of passing lambdas as attributes.
One possible solution would be to use function calls, instead of attributes. Not as pretty, but could solve your problem without duplicate code:
private void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
[Theory]
public Test1()
{
HasConfiguration((Config y) => y.Param1);
}
[Theory]
public Test2()
{
HasConfiguration((Config y) => y.Param2);
}
来源:https://stackoverflow.com/questions/45299470/lambda-expression-as-inline-data-in-xunit