【问题标题】:Lambda expression as inline data in xUnitLambda 表达式作为 xUnit 中的内联数据
【发布时间】:2017-12-31 03:41:45
【问题描述】:

我对 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 表达式转换为类型 'object[]',因为它不是委托类型”,这非常清楚。

你知道如何克服这个问题吗?

【问题讨论】:

    标签: c# .net unit-testing xunit


    【解决方案1】:

    奇怪的委托不是对象,但Actions 或Funcs 是。为此,您必须将 lambda 转换为其中一种类型。

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

    但是这样做,你的表情不再是一成不变的。因此,这将阻止在 Attribute 中使用。

    无法将 lambdas 作为属性传递。

    一种可能的解决方案是使用函数调用,而不是属性。不那么漂亮,但可以在没有重复代码的情况下解决您的问题:

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

    【讨论】:

      【解决方案2】:

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

      我可能会想出一些更好或更简洁的东西,但现在它可以工作并且代码不会重复。

      【讨论】:

        【解决方案3】:

        除了已经发布的答案。可以通过直接生成 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));
        }
        

        【讨论】:

        • 虽然它似乎是一个更好的解决方案,但我不太喜欢它,因为它会在测试资源管理器中显示为单个测试。我希望看到所有必需的参数(就像我提供的答案一样)。还是谢谢你!
        • 真的,这似乎是测试资源管理器中的一个错误。
        【解决方案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);
            }
        }
        

        【讨论】:

          【解决方案5】:
          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
                          }
                      }
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-08-25
            • 1970-01-01
            • 2013-07-02
            • 1970-01-01
            • 1970-01-01
            • 2011-03-19
            相关资源
            最近更新 更多