【问题标题】:How to write integration test for update function in repository services with dapper and nunit?如何使用 dapper 和 nunit 在存储库服务中编写更新功能的集成测试?
【发布时间】:2021-06-07 15:51:20
【问题描述】:

我正在开发一个 blazor 服务器端项目。

当我尝试为我的存储库模式服务编写集成测试时,我得到了错误: System.PlatformNotSupportedException : This platform does not support distributed transactions.。 尝试使用第二个连接并查询是否真正保存了所需的数据会触发错误。

我的测试代码:


namespace CVUnitTests.IntegrationTests.Services
{
    public class SkilServiceTest
    {
        [TestFixture]
        public class YourFixture
        {
            private TransactionScope _scope;
            private ISkillService _skillService;
            private IConfigurationRoot _config;

                [SetUp]
            public void SetUp()
            {
               _config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
               _skillService = new SkillService(_config);
               _scope = new TransactionScope();
            }

            [TearDown]
            public void TearDown()
            {
                _scope.Dispose();
            }


            [Test]
            public async Task UpdateSkill_NewSkillIsSaved()
            {
                var skillToUpdate = new Skill()
                {
                    Name = "TestSkill",
                    Type = SkillType.Hardskill,
                    Category = "TestCategory"
                };
                await _skillService.UpdateSkill(skillToUpdate);
                using IDbConnection connection = new SqlConnection(DatabaseUtils.GetConnectionString(_config));
                var sql = "SELECT * FROM skill WHERE Name = TestSkill";
                var queriedSkill = connection.QuerySingle<Skill>(sql);
                Assert.AreEqual(queriedSkill, skillToUpdate);
            }
        }
    }
}

我发现Query 可用于实现此目的,但我无法将两个查询包装成一个使用,因为该服务正在打开自己的连接:

public Task<bool> UpdateSkill(Skill skill)
        {
            if (GetSkill(skill.Id) != null)
            {
                using IDbConnection dbConnection = new SqlConnection(_connectionString);
                var parameters = new
                {
                    Id = skill.Id,
                    skill.Name,
                    Skilltype = skill.Type == SkillType.Hardskill ? "TRUE" : "FALSE"
                };
                var sql = "update skill SET Name = @Name, Skilltype = @Skilltype where id = @Id";
                dbConnection.Open();
                var result = dbConnection.ExecuteAsync(sql, parameters).Result;
                dbConnection.Close();
                return Task.FromResult(true);
            }
            else
            {
                using IDbConnection dbConnection = new SqlConnection(_connectionString);
                var parameters = new {skill.Name, Skilltype = skill.Type == SkillType.Hardskill ? 1 : 0};
                var sql = "insert into skill (Name, Skilltype) values (@Name, @Skilltype)";
                dbConnection.Open();
                var result = dbConnection.ExecuteAsync(sql, parameters).Result;
                dbConnection.Close();
                return Task.FromResult(false);
            }

即使我只在事务范围内运行一个查询,我也会收到错误 This platform does not support distributed transactions.

我还尝试从我的服务中读出dbConnection,而不是创建一个新的,但也没有成功。

我实际上可以在我的服务函数中使用多个,然后返回技能,尽管这似乎是在我的方法中添加测试代码。

有没有办法我仍然可以实现我想要的测试行为,还是我必须完全改变我的测试逻辑?

【问题讨论】:

  • using(connection.BeginTransaction())代替TransactionScope不要忘记所有DB对象都应该在using块中,而不是缓存在字段中
  • 我尝试在更新答案时添加connection.BeginTransaction(),但我仍然遇到同样的错误。我是否也必须以某种方式在方法中使用它?
  • 你不能只改变一个方法来解决这个问题。 UpdateSkill 使用自己的连接。使其受外部事务控制的唯一方法是使用分布式事务。你必须重新考虑整个Repository 类。包括你为什么首先添加它。在可扩展性方面最大的进步之一是 ADO.NET 和 ORM(如 Entity Framework)支持的断开连接的操作。所有更改都被缓存,并且在代码明确要求之前没有任何内容被持久化。如果您想回滚,请不要保存更改。
  • 为了从使用 Dapper 中获得任何性能优势,应该避免使用像 GetSkill 这样的方法。使用 Dapper 的全部意义在于 避免 读取对象只是为了更新它,但这正是 GetSkill 所做的。更糟糕的是,您必须使用事务来确保结果一致。您可以编写单个“UPSERT”SQL 批处理,方法是使用MERGE 命令或UPDATEINSERT 以及只允许其中一个完成的WHERE 子句。在这两种情况下,您都必须在查询中使用事务(MERGE 不是原子的)但该事务将是短暂的。
  • @FelixOuttaSpace repository 用于抽象存储,而不是使测试更容易。不过这是一个好处。像 EF Core 这样的 ORM 提供了相同的好处,甚至更多。普通的 ADO.NET 也是如此。 DataTable 对将保存到的数据库一无所知。但是,一旦您开始使用事务,您必须考虑连接和事务生命周期。

标签: nunit integration-testing repository-pattern dapper blazor-server-side


【解决方案1】:

终于用下面的代码让它运行起来了:

{
    public class SkilServiceTest
    {
        [TestFixture]
        public class YourFixture
        {
            private TransactionScope _scope;
            private ISkillService _skillService;
            private IConfigurationRoot _config;
            private DatabaseUtils _databaseUtils;
                
            [SetUp]
            public void SetUp()
            {
               _config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
               _skillService = new SkillService(_config);
               
               _databaseUtils = new DatabaseUtils(_config);
               //wipe data and create empty table to execute tests on empty tables 
               _databaseUtils.DropTable("Skill");
               _databaseUtils.CreateSkillTable();
            }

            [TearDown]
            public void TearDown()
            {
                //wipe data so database manipulation within tests has no effect afterwards and does not interfere later tests
                _databaseUtils.DropTable("Skill");
            }

            
            

            [Test]
            public async Task UpdateSkill_NewSkillIsSaved()
            {
                Skill skillToUpdate = new Skill()
                {
                    Name = "TestSkill",
                    Type = SkillType.Softskill,
                    Category = "SomeCategory",
                    Id = 1
                };

                await _skillService.UpdateSkill(skillToUpdate);
                string sql_query = "SELECT * FROM skill WHERE name = \'TestSkill\'";
                Skill queriedSkill = null;
                using (IDbConnection connection = new SqlConnection(DatabaseUtils.GetConnectionString(_config)))
                {
                    connection.Open();
                    queriedSkill = connection.QuerySingle<Skill>(sql_query); 
                }
                
                Assert.IsTrue(queriedSkill.Equals(skillToUpdate));
            }
        }
    }

也必须更改Update

 public async Task<bool> UpdateSkillAsync(Skill skill)
        {
                        
            var parameters = new {Id = skill.Id};
            var sql_query = "SELECT * FROM skill WHERE id = @Id";
            var queriedSkill = DbConnection_second.QuerySingleOrDefault<Skill>(sql_query, parameters);

            if (queriedSkill != null)
            {
                var sql = "update skill SET Name = @Name, Type = @Type, Category = @Category where id = @Id";
                var result = await DbConnection.ExecuteAsync(sql, skill);
                return true;
            }
            else
            {
                var sql = "insert into skill (Name, Type, Category) values (@Name, @Type, @Category)";
                var result = await DbConnection.ExecuteAsync(sql, skill);
                return false;
            } 
        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-24
    • 2021-07-28
    • 1970-01-01
    • 1970-01-01
    • 2017-03-25
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多