You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# EF操作SQL Server的NUnit测试报DbContext未配置提供程序错误

问题根因

单元测试运行时触发No database provider has been configured异常的核心原因是:测试项目运行在独立的执行上下文,没有加载主项目的连接字符串配置,PhonebookContext的OnConfiguring方法中读取ConnectionStrings["SQLServer"]时返回null,调用.ConnectionString抛出空引用异常,被内部catch块捕获后仅打印日志,未完成SQL Server提供程序的配置,最终EF Core在执行操作时抛出异常。

现存问题
  • 测试项目缺失配置:主项目正常运行时会读取自身输出目录下配置文件中的连接字符串,但测试项目运行时不会自动复制、加载主项目的配置文件,ConfigurationManager无法找到名为SQLServer的连接字符串项,导致配置流程中断。
  • 断言写法错误:现有测试使用Assert.Equals做判断,该方法是object基类的静态Equals方法,不会触发NUnit的断言逻辑,且Contact类默认做引用相等判断,即使属性完全一致,两个不同实例也会判定为不相等。
  • 测试逻辑强依赖真实数据库:现有测试直接连接真实SQL Server,属于集成测试范畴,执行时会产生持久化脏数据,测试稳定性受数据库服务运行状态影响。
  • 代码硬编码依赖:Create方法内部直接实例化PhonebookContext,没有预留注入入口,测试时无法替换为测试专用的数据库上下文。
修复方案

快速修复(解决当前配置报错,可直接运行现有测试逻辑)

  1. 在测试项目中新增App.config配置文件,添加和主项目一致的连接字符串节点,示例配置:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="SQLServer" connectionString="替换为测试环境可用的SQL Server连接字符串" providerName="System.Data.SqlClient" />
    </connectionStrings>
</configuration>
  1. 确认测试项目已安装和主项目版本完全一致的Microsoft.EntityFrameworkCore.SqlServerNuGet包,避免版本不兼容导致提供程序加载失败。
  2. 修正测试断言的写法:
[Test]
public void Create_Contact_DbRowIsAdded()
{
    var contact = new Contact { Name = "Abhinav", PhoneNumber = 1234567890, };
    SqlAccess.Create(contact);
    
    var lastContact = SqlAccess.GetLastContact();
    Assert.That(lastContact.Name, Is.EqualTo(contact.Name));
    Assert.That(lastContact.PhoneNumber, Is.EqualTo(contact.PhoneNumber));
}

注意:测试执行前需要确保配置中指向的SQL Server实例可正常访问,且测试执行完成后需要手动清理插入的测试数据,避免影响其他测试用例。

规范改造方案(推荐,实现不依赖外部服务的稳定单元测试)

  1. 改造PhonebookContext,支持外部传入配置项,兼容生产环境和测试场景:
public class PhonebookContext : DbContext
{
    // 保留无参构造函数供生产环境原有逻辑使用
    public PhonebookContext() { }
    // 增加带配置参数的构造函数,供测试场景注入测试用配置
    public PhonebookContext(DbContextOptions<PhonebookContext> options) : base(options) { }

    public DbSet<Contact> Contacts => Set<Contact>();
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 仅当外部没有传入配置时,才读取本地配置文件的连接字符串
        if (!optionsBuilder.IsConfigured)
        {
            var connStr = ConfigurationManager.ConnectionStrings["SQLServer"]?.ConnectionString;
            if (!string.IsNullOrEmpty(connStr))
            {
                optionsBuilder.UseSqlServer(connStr);
            }
        }
    }
}
  1. 改造SqlAccess的静态方法,支持外部传入DbContext实例,避免硬编码依赖:
public static void Create(Contact contact, PhonebookContext context = null)
{
    using (var db = context ?? new PhonebookContext())
    {
        db.Database.EnsureCreated();
        try
        {
            db.Contacts.Add(contact);
            db.SaveChanges();
            Console.WriteLine($"Successfully added {contact.Name}!"); 
        }
        catch
        {
            Console.WriteLine(Helpers.CreateErrorMessage, contact.Name);
        }
    }
}
  1. 测试项目引入EF Core内存数据库提供程序,替换真实SQL Server作为测试数据源,不需要额外配置数据库服务:
  • 首先在测试项目安装Microsoft.EntityFrameworkCore.InMemoryNuGet包,版本和项目使用的EF Core版本保持一致。
  • 改造测试用例:
[Test]
public void Create_Contact_DbRowIsAdded()
{
    // 构建内存数据库配置,每个测试用例使用独立的数据库名,避免用例间数据干扰
    var options = new DbContextOptionsBuilder<PhonebookContext>()
        .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
        .Options;

    var contact = new Contact { Name = "Abhinav", PhoneNumber = 1234567890 };

    // 插入数据使用独立上下文实例
    using (var createContext = new PhonebookContext(options))
    {
        SqlAccess.Create(contact, createContext);
    }

    // 验证数据使用新的上下文实例,模拟真实场景下的数据库读取逻辑
    using (var verifyContext = new PhonebookContext(options))
    {
        var lastContact = verifyContext.Contacts.OrderByDescending(c => c.Id).First();
        Assert.That(lastContact.Name, Is.EqualTo(contact.Name));
        Assert.That(lastContact.PhoneNumber, Is.EqualTo(contact.PhoneNumber));
    }
}

内容的提问来源于stack exchange,提问作者Abhinav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 17:31:05