代码之家  ›  专栏  ›  技术社区  ›  Learning Curve

如何在XUnit单元测试中使用microsoft.extensions.configuration.iconiguration

  •  2
  • Learning Curve  · 技术社区  · 7 年前

    在我的ASP.NET Core 2.0应用程序中,我尝试对使用 Microsoft.extensions.configuration.icon配置 依赖注入。 我用的是Xunit,不知道怎么通过 IConfiguration 从我的单元测试课。我尝试了以下实现并得到了错误

    消息:以下构造函数参数没有匹配的fixture数据:IConfiguration配置。

    我对测试框架非常陌生,甚至不知道是否可以像我在代码片段中所做的那样使用依赖注入。

    我的单元测试课程如下

    public class SqlRestaurantDataCLUnitTest
    {
        private readonly IConfiguration configuration;
        public SqlRestaurantDataCLUnitTest(IConfiguration configuration)
        {
            this.configuration = configuration;
        }
        [Fact]
        public void AddTest()
        {
            var restaurantDataCL = new SqlRestaurantDataCL(configuration);
            var restaurant = new Restaurant
            {
                Name = "TestName",
                Cuisine = CuisineType.None
            };
    
            var result = restaurantDataCL.Add(restaurant);
    
            Assert.IsNotType(null, result.Id);    
        }
    }
    

    我的数据服务层如下

    public class SqlRestaurantDataCL : IRestaurantDataCL
    {
        private readonly IConfiguration configuration;
        public SqlRestaurantDataCL(IConfiguration configuration)
        {
            this.configuration = configuration;
        }
        public Restaurant Add(Restaurant restaurant)
        {
            using (var db = GetConnection())
            {
                string insertSql = @"INSERT INTO [dbo].[RESTAURANTS]([Cuisine], [Name]) 
                                    OUTPUT INSERTED.*
                                    VALUES (@Cuisine, @Name)";
    
                restaurant = db.QuerySingle<Restaurant>(insertSql, new
                {
                    Cuisine = restaurant.Cuisine,
                    Name = restaurant.Name
                });
    
                return restaurant;
            }
        }
    
        private IDbConnection GetConnection()
        {
            return new SqlConnection(configuration.GetSection(Connection.Name).Value.ToString());
        }
    }
    
    public class Connection
    {
        public static string Name
        {
            get { return "ConnectionStrings: OdeToFood"; }
        }
    }
    
    1 回复  |  直到 7 年前
        1
  •  1
  •   Nkosi    7 年前

    单元测试有一个非常有用的暴露设计问题的习惯。在本例中,由于与框架关注点以及静态关注点的紧密耦合,您已经做出了一些设计选择,这些选择被证明很难测试。

    首先,看起来 SqlRestaurantDataCL 实际上取决于连接工厂

    public interface IDbConnectionFactory {
        IDbConnection GetConnection();
    }
    

    它将根据建议重构数据实现,以依赖于该抽象。

    public class SqlRestaurantDataCL : IRestaurantDataCL {
        private readonly IDbConnectionFactory factory;
    
        public SqlRestaurantDataCL(IDbConnectionFactory factory) {
            this.factory = factory;
        }
        public Restaurant Add(Restaurant restaurant) {
            using (var connection = factory.GetConnection()) {
                string insertSql = @"INSERT INTO [dbo].[RESTAURANTS]([Cuisine], [Name]) 
                                    OUTPUT INSERTED.*
                                    VALUES (@Cuisine, @Name)";
    
                restaurant = connection.QuerySingle<Restaurant>(insertSql, new {
                    Cuisine = restaurant.Cuisine,
                    Name = restaurant.Name
                });
    
                return restaurant;
            }
        }
    
        //...
    }
    

    假设dapper正用于进行上述查询。

    通过引入抽象的依赖项,可以在隔离测试时根据需要模拟它们。

    public class SqlRestaurantDataCLUnitTest {
    
        [Fact]
        public void AddTest() {
            //Arrange
            var connection = new Mock<IDbConnection>();
            var factory = new Mock<IDbConnectionFactory>();
            factory.Setup(_ => _.GetConnection()).Returns(connection.Object);
    
            //...setup the connection to behave as expected
    
            var restaurantDataCL = new SqlRestaurantDataCL(factory.Object);
            var restaurant = new Restaurant {
                Name = "TestName",
                Cuisine = CuisineType.None
            };
    
            //Act
            var result = restaurantDataCL.Add(restaurant);
    
            //Assert
            Assert.IsNotType(null, result.Id);
        }
    }
    

    现在,如果您打算实际接触真实的数据库,那么这不是一个隔离单元测试,而是一个集成测试,它将采用不同的方法。

    在生产代码中,工厂可以实现

    public class SqlDbConnectionFactory : IDbConnectionFactory {
        private readonly ConnectionSetings connectionSettings;
    
        SqlDbConnectionFactory(ConnectionSetings connectionSettings) {
            this.connectionSettings = connectionSettings;
        }
    
        public IDbConnection GetConnection() {
            return new SqlConnection(connectionSettings.Name));
        }
    }
    

    在哪里? ConnectionSetings 定义为存储连接字符串的简单POCO

    public class ConnectionSetings {
        public string Name { get; set; }
    }
    

    在合成根目录中,可以从配置中提取设置。

    IConfiguration Configuration; //this would have been set previously
    
    public void ConfigureServices(IServiceCollection services) {
        //...
    
        var settings = Configuration
            .GetSection("ConnectionStrings:OdeToFood")
            .Get<ConnectionSetings>();
    
        //...verify settings (if needed)
    
        services.AddSingleton(settings);
        services.AddSingleton<IDbConnectionFactory,SqlDbConnectionFactory>();
        services.AddSingleton<IRestaurantDataCL,SqlRestaurantDataCL>();
        //Note: while singleton was used above, You can decide to use another scope
        //      if so desired.
    }
    

    真的没必要路过 IConfiguration 因为它更像是一个框架问题,实际上只在启动时才相关。