Lambda 表达式作为 xUnit 中的内联数据
Lambda expression as inline data in xUnit
我是 xUnit 的新手,这是我想要实现的目标:
[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));
}
基本上,我有一个 GenericConfig 对象,其中包含 Config 和其他类型的配置,但我需要检查每个参数已验证。因为它们都是字符串,所以我想使用 [InlineData] 属性来简化,而不是编写 N 等于测试。
不幸的是,我得到的错误是 "Cannot convert lambda expression to type 'object[]' because it's not a delegate type",这非常清楚。
你知道如何克服这个问题吗?
奇怪的是,委托不是对象,但 Action
或 Func
是。为此,您必须将 lambda 转换为这些类型之一。
object o = (Func<Config, string>)((Config y) => y.Param1)
但是这样一来,你的表情就不一样了。所以这将阻止在 Attribute
.
中使用
无法将 lambda 作为属性传递。
一种可能的解决方案是使用函数调用,而不是属性。不那么漂亮,但可以在没有重复代码的情况下解决您的问题:
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);
}
实际上,我找到了一个比Iqon提供的解决方案更好的解决方案(谢谢!)。
显然,InlineData
属性只支持原始数据类型。如果您需要更复杂的类型,可以使用 MemberData
属性为单元测试提供来自自定义数据提供程序的数据。
我是这样解决问题的:
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;
}
}
}
测试方法如下:
[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));
}
我也许可以想出一些更好或更简洁的东西,但现在它可以工作并且代码没有重复。
除了已经发布的答案。可以通过直接产生 lambda 来简化测试用例。
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) };
}
}
}
此测试 ConfigTestDataProvider
然后可以直接注入 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));
}
我遇到了和你一样的问题,我找到了使用 TheoryData
class 和 MemberData
属性的解决方案。这是示例,我希望代码有用:
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);
}
}
public class HrcpDbTests
{
[Theory]
[MemberData(nameof(TestData))]
public void Test(Expression<Func<bool>> exp)
{
// Arrange
// Act
// Assert
}
public static IEnumerable<object[]> TestData
{
get
{
Expression<Func<bool>> mockExp1 = () => 1 == 0;
Expression<Func<bool>> mockExp2 = () => 1 != 2;
return new List<object[]>
{
new object[]
{
mockExp1
},
new object[]
{
mockExp2
}
}
}
}
}
我是 xUnit 的新手,这是我想要实现的目标:
[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));
}
基本上,我有一个 GenericConfig 对象,其中包含 Config 和其他类型的配置,但我需要检查每个参数已验证。因为它们都是字符串,所以我想使用 [InlineData] 属性来简化,而不是编写 N 等于测试。
不幸的是,我得到的错误是 "Cannot convert lambda expression to type 'object[]' because it's not a delegate type",这非常清楚。
你知道如何克服这个问题吗?
奇怪的是,委托不是对象,但 Action
或 Func
是。为此,您必须将 lambda 转换为这些类型之一。
object o = (Func<Config, string>)((Config y) => y.Param1)
但是这样一来,你的表情就不一样了。所以这将阻止在 Attribute
.
无法将 lambda 作为属性传递。
一种可能的解决方案是使用函数调用,而不是属性。不那么漂亮,但可以在没有重复代码的情况下解决您的问题:
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);
}
实际上,我找到了一个比Iqon提供的解决方案更好的解决方案(谢谢!)。
显然,InlineData
属性只支持原始数据类型。如果您需要更复杂的类型,可以使用 MemberData
属性为单元测试提供来自自定义数据提供程序的数据。
我是这样解决问题的:
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;
}
}
}
测试方法如下:
[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));
}
我也许可以想出一些更好或更简洁的东西,但现在它可以工作并且代码没有重复。
除了已经发布的答案。可以通过直接产生 lambda 来简化测试用例。
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) };
}
}
}
此测试 ConfigTestDataProvider
然后可以直接注入 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));
}
我遇到了和你一样的问题,我找到了使用 TheoryData
class 和 MemberData
属性的解决方案。这是示例,我希望代码有用:
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);
}
}
public class HrcpDbTests
{
[Theory]
[MemberData(nameof(TestData))]
public void Test(Expression<Func<bool>> exp)
{
// Arrange
// Act
// Assert
}
public static IEnumerable<object[]> TestData
{
get
{
Expression<Func<bool>> mockExp1 = () => 1 == 0;
Expression<Func<bool>> mockExp2 = () => 1 != 2;
return new List<object[]>
{
new object[]
{
mockExp1
},
new object[]
{
mockExp2
}
}
}
}
}