【问题标题】:XUnit mocked db connection dapper error, 'Object is not set to an instance of an object' when executing sql statementXUnit mocked db connection dapper error, 'Object is not set to an instance of an object' 执行 sql 语句时
【发布时间】:2018-07-24 20:32:28
【问题描述】:

我正在尝试按照@NKosi Here 的建议对我的服务层进行单元测试。我能够通过实现实际的工厂实现成功地进行集成测试,而无需模拟任何东西,但无法进行单元测试(通过模拟 IDbConnection 和我的 SQL 连接工厂类),因为 Dapper 查询执行失败并出现错误“对象未设置为一个对象的实例”。

我的 IDbConnection 工厂及其实现如下

public interface IDbConnectionFactory
{
    IDbConnection CreateConnection();
}

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

public class SqlConnectionFactory : IDbConnectionFactory
{
    private readonly ConnectionSetings connectionSettings;

    public SqlConnectionFactory(ConnectionSetings connectionSettings)
    {
        this.connectionSettings = connectionSettings;
    }
    public IDbConnection CreateConnection()
    {
        return new SqlConnection(connectionSettings.Name);
    }
}

XUnit 测试如下

    [Fact]
    public void Get_RestaurantById_ReturnsRestaurant()
    {
        //Arrange
        var connection = new Mock<IDbConnection>();
        var dbConnectionFactory = new Mock<IDbConnectionFactory>();
        dbConnectionFactory.Setup(x => x.CreateConnection()).Returns(connection.Object);

        //Act
        var result = new SqlRestaurantDataCL(dbConnectionFactory.Object).Get(1);

        //Assert
        result.Name.Equals("Test Name 1");
        //Assert.Equal("Test Name 1", result.Name);
    }

服务层如下

public class SqlRestaurantDataCL : IRestaurantDataCL
{
    private readonly IDbConnectionFactory factory;
    public SqlRestaurantDataCL(IDbConnectionFactory factory)
    {
        this.factory = factory;
    }

    public Restaurant Get(int id)
    {
        using (var connection = factory.CreateConnection())
        {
            var selectSql = @"SELECT * From Restaurants Where Id = @Id";

            var restaurant = connection.QuerySingleOrDefault<Restaurant>(selectSql, new
            {
                id
            });

            return restaurant;
        }
    }
}

以下是错误截图

【问题讨论】:

  • 这里的问题是,在我看来,设置模拟连接以安全导航QuerySingleOrDefault Dapper 扩展所需的模拟量并不值得头疼。这是试图模拟你不拥有或控制的代码的一个主要问题,比如 3rd 方依赖项,在这种情况下是 Dapper
  • 我个人会在我的代码和扩展之间添加另一层抽象来解决耦合问题。
  • 你的意思是某种业务逻辑层,它会实例化数据库连接并调用数据服务层,这样我就可以通过模拟业务逻辑层来通过 dapper 障碍?
  • 基本上不是 dapper 扩展的包装器,它允许您在没有所有连锁反应的情况下模拟行为
  • 但我个人不会对SqlRestaurantDataCL 进行单元测试,因为我认为它是直接与数据源交互的最低级别之一。我会专注于依赖 IRestaurantDataCL 的内容并为这些测试模拟它

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


【解决方案1】:

如果有人处于类似情况,以下是我的问题的答案。在遵循这个解决方案之前,我建议阅读上面的@NKosi cmets 并参考@Mikhail 的解决方案Here

ServiceStack.OrmLite.Sqlite 包添加到内存中使用

internal class InMemoryDatabase
{
    private readonly OrmLiteConnectionFactory dbFactory = new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider);

    public IDbConnection OpenConnection() => this.dbFactory.OpenDbConnection();

    public void Insert<T>(IEnumerable<T> items)
    {
        using (var db = this.OpenConnection())
        {
            db.CreateTableIfNotExists<T>();
            foreach (var item in items)
            {
                db.Insert(item);
            }
        }
    }
}

数据访问层如下

    public IEnumerable<Restaurant> GetAll()
    {
        using (var connection = factory.CreateConnection())
        {
            //return connection.Query<Restaurant>("Select * From [dbo].[Restaurants] Order By Name");
            return connection.Query<Restaurant>("Select * From Restaurant Order By Name");
        }
    }

单元测试如下

    [Fact]
    public void Get_RestaurantById_ReturnsRestaurant()
    {
        //Arrange
        var restaurants = new List<Restaurant>
        {
            new Restaurant { Id = 1, Name = "Test Name 1", Cuisine = CuisineType.None},
            new Restaurant { Id = 2, Name = "Test Name 2", Cuisine = CuisineType.French},
            new Restaurant { Id = 3, Name = "Test Name 3", Cuisine = CuisineType.German},
            new Restaurant { Id = 4, Name = "Test Name 4", Cuisine = CuisineType.Italian},
            new Restaurant { Id = 5, Name = "Test Name 5", Cuisine = CuisineType.None}
        };

        var db = new InMemoryDatabase();
        db.Insert(restaurants);

        var connection = new Mock<IDbConnection>();
        var dbConnectionFactoryMock = new Mock<IDbConnectionFactory>();
        dbConnectionFactoryMock.Setup(c => c.CreateConnection()).Returns(db.OpenConnection());

        //Act
        var result = new SqlRestaurantDataCL(dbConnectionFactoryMock.Object).GetAll();

        //Assert            
        result.Should().BeEquivalentTo(restaurants);

    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-02-04
    • 1970-01-01
    • 2012-01-02
    • 2017-01-08
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    相关资源
    最近更新 更多