【问题标题】:How to use Microsoft.Extensions.Configuration.IConiguration in my XUnit unit testing如何在我的 XUnit 单元测试中使用 Microsoft.Extensions.Configuration.IConiguration
【发布时间】:2018-12-26 10:33:01
【问题描述】:

在我的 Asp.net Core 2.0 应用程序中,我尝试对使用 Microsoft.Extensions.Configuration.IConfiguration 依赖注入的数据服务层(.Net 标准类库)进行单元测试。 我正在使用 XUnit,但不知道如何从我的单元测试类中传递 IConfiguration。我尝试了以下实现并收到错误

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

我对测试框架真的很陌生,甚至不知道是否可以像我在我的代码 sn-p 中尝试那样使用依赖注入。

我的单元测试类如下

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

【问题讨论】:

  • 你为什么还要传递IConfiguration 对象呢?您不应该有一个不错的 POCO,并将您的所有设置都作为属性吗?无论如何,只需创建一个实现 IConfiguration 并返回适合您测试的设置的对象。
  • @David 你能用一些代码示例解释一下吗?
  • 我必须同意 DavidG 关于 IConfiguration 依赖的观点。如何使用此依赖项。显示SqlRestaurantDataCL.GetConnection()。这可能最终成为 XY problem 中掩盖的设计问题
  • @Nkosi 我已经用 GetConnection() 代码更新了我的问题。

标签: unit-testing asp.net-core-mvc asp.net-core-2.0 xunit xunit.net


【解决方案1】:

单元测试有一个非常有用的暴露设计问题的习惯。在这种情况下,由于与框架关注点和静态关注点紧密耦合,您做出了一些难以测试的设计选择。

首先,看起来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,因为它更多的是一个框架问题,实际上只在启动时才相关。

【讨论】:

  • 您的回答确实帮助我理解了这个概念。谢谢!
  • 它抱怨 factory.Setup(_=> _.GetConnection()).Returns(connection) 中的连接。它说,不能从 'Moq.Mock' 转换为 'System.Data.IDbConnection'
  • @LearningCurve 这是一个错字。固定。
  • @LearningCurve 看看这篇文章mikhail.io/2016/02/unit-testing-dapper-repositories
  • @LearningCurve 显然对孤立的单元测试来说,小巧玲珑是一种痛苦。找到另一个可能有帮助的库github.com/half-ogre/dapper-wrapper
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多