xUnit中的Lambda表达式作为内联数据

11

我对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等测试。

不幸的是,我收到的错误是“无法将lambda表达式转换为'type'object []',因为它不是委托类型”,这很明显。

你有任何想法如何克服这个问题吗?

5个回答

10

实际上,我找到了一个比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));
}

我可能可以想出更好或更干净的解决方案,但现在这个可用,并且代码没有重复。


10
除了已发布的答案外,测试用例可以通过直接产生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可以直接注入Lambda表达式。

[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));
}

3
虽然它看起来是一个更好的解决方案,但我并不是很喜欢它,因为它会在测试浏览器中显示为一个单独的测试。相比之下,我宁愿看到所有需要的参数(就像我提供的答案一样)。无论如何,还是谢谢你! - xTuMiOx
实际上,这似乎是测试资源管理器中的一个错误。 - Iqon

4

我和你一样遇到了这个问题,不过我找到了解决方案——使用 TheoryData 类和 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);
    }
}

1

奇怪的是,代理并不是对象,但ActionFunc是。为了做到这一点,你需要将lambda表达式转换为其中的一种类型。

 object o = (Func<Config, string>)((Config y) => y.Param1)

但是这样做,你的表达式就不再是常数了。因此这将防止在属性中使用。
无法将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);
}

0
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
                }
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接