如何为基于SQL Server与EF Core的ASP.NET Core应用编写测试及控制器单元测试
ASP.NET Core控制器单元测试(EF Core+SQL Server场景)
测试优先级
先测服务层,再测控制器。服务是业务逻辑的核心,控制器仅负责请求转发、参数校验、视图/结果返回,本质是依赖服务的中间层。先覆盖服务核心逻辑的测试,再验证控制器对服务的调用逻辑,能让测试更高效,问题定位更清晰。
控制器单元测试:Mock依赖服务
控制器单元测试的核心是隔离外部依赖,绝对不能直接连接生产SQL Server。用Moq这类框架模拟服务的行为,验证控制器的输出是否符合预期即可。
示例代码
先看基础的控制器、服务定义:
// 目标控制器 public class HomeController : Controller { private readonly IDataService _service; public HomeController(IDataService service) { _service = service; } public IActionResult Index() { var data = _service.GetAll(); return View(data); } } // 服务接口 public interface IDataService { IEnumerable<MyModel> GetAll(); } // 实体模型 public class MyModel { public int Id { get; set; } public string Name { get; set; } }
以下是控制器的单元测试(基于xunit+Moq):
public class HomeControllerTests { private readonly Mock<IDataService> _mockService; private readonly HomeController _controller; public HomeControllerTests() { // 初始化Mock服务实例 _mockService = new Mock<IDataService>(); // 注入Mock服务到控制器 _controller = new HomeController(_mockService.Object); } [Fact] public void Index_ReturnsViewWithCorrectData() { // 1. 准备测试数据 var testData = new List<MyModel> { new() { Id = 1, Name = "测试数据1" }, new() { Id = 2, Name = "测试数据2" } }; // 2. 配置Mock行为:调用GetAll时返回预设数据 _mockService.Setup(s => s.GetAll()).Returns(testData); // 3. 执行控制器方法 var result = _controller.Index(); // 4. 验证结果 // 确认返回类型是ViewResult var viewResult = Assert.IsType<ViewResult>(result); // 确认视图模型的类型和内容正确 var model = Assert.IsAssignableFrom<IEnumerable<MyModel>>(viewResult.Model); Assert.Equal(2, model.Count()); // 验证服务的GetAll方法确实被调用了一次 _mockService.Verify(s => s.GetAll(), Times.Once); } }
服务层测试:用EF Core内存数据库
服务依赖EF Core的DbContext,测试时推荐用内存数据库模拟真实数据库行为——既隔离了生产环境,又能验证EF Core查询逻辑的正确性。
示例代码
public class DataServiceTests { [Fact] public void GetAll_ReturnsAllDatabaseRecords() { // 1. 配置独立内存数据库(每个测试用唯一名称,避免数据污染) var dbOptions = new DbContextOptionsBuilder<AppDbContext>() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; // 2. 初始化测试数据 using (var context = new AppDbContext(dbOptions)) { context.MyModels.AddRange( new MyModel { Id = 1, Name = "测试数据1" }, new MyModel { Id = 2, Name = "测试数据2" } ); context.SaveChanges(); } // 3. 实例化服务并执行测试 using (var context = new AppDbContext(dbOptions)) { var service = new DataService(context); var result = service.GetAll(); // 验证返回数据数量正确 Assert.Equal(2, result.Count()); } } }
其他测试类型:集成测试
如果要验证从HTTP请求到视图渲染的完整链路正确性,可以用集成测试。通过Microsoft.AspNetCore.Mvc.Testing包启动测试服务器,模拟真实请求流程。
示例代码
public class HomeControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>> { private readonly HttpClient _httpClient; public HomeControllerIntegrationTests(WebApplicationFactory<Program> factory) { // 创建测试用HttpClient _httpClient = factory.CreateClient(); } [Fact] public async Task Index_Page_ReturnsSuccessAndContainsData() { // 发送GET请求到首页 var response = await _httpClient.GetAsync("/"); // 验证请求成功 response.EnsureSuccessStatusCode(); // 验证页面内容包含预期数据(假设视图渲染了模型的Name字段) var pageContent = await response.Content.ReadAsStringAsync(); Assert.Contains("测试数据1", pageContent); } }
常见错误修正
如果你的测试直接使用真实DbContext连接SQL Server,这不属于单元测试——这类测试依赖数据库状态,不稳定且执行缓慢。正确的做法是:
- 控制器测试:必须Mock服务,完全隔离数据库
- 服务测试:用内存数据库或Mock DbSet,避免依赖生产环境
内容的提问来源于stack exchange,提问作者py_mc
相关产品推荐
相关产品推荐

