ASP.NET Core 3 MVC控制器新手单元测试编写求助
.NET Core Controller 单元测试实现方案
前置依赖准备
测试项目需要先安装以下Nuget包,版本和你项目主版本保持一致即可:
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" /> <PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.25" />
Create 方法单元测试示例
测试逻辑覆盖:提交合法公司信息时,数据正确入库且跳转至列表页
using Xunit; using Moq; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; // 替换为你项目对应命名空间 using YourProject.Controllers; using YourProject.Data; using YourProject.Models; using YourProject.Services; public class CompanyControllerTests { // 构造独立的内存数据库上下文,避免多测试用例数据干扰 private CodingChallegeDBContext GetTestDbContext() { var options = new DbContextOptionsBuilder<CodingChallegeDBContext>() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; var context = new CodingChallegeDBContext(options); context.Database.EnsureCreated(); return context; } [Fact] public async Task Create_ValidModel_AddsCompanyAndRedirects() { // 初始化依赖 var mockPageService = new Mock<IPaginatedListService>(); var testDbContext = GetTestDbContext(); var controller = new CompanyController(testDbContext, mockPageService.Object); // 构造测试数据 var testCompany = new Company { Id = 1, Name = "测试科技有限公司", Address = "北京市海淀区测试路1号" // 补全你Company模型所有必填字段 }; // 执行测试方法 var result = await controller.Create(testCompany); // 断言逻辑 // 验证返回结果为跳转到Index页 var redirectRes = Assert.IsType<RedirectToActionResult>(result); Assert.Equal(nameof(CompanyController.Index), redirectRes.ActionName); // 验证数据已正确写入数据库 var savedCompany = await testDbContext.Companies.FindAsync(1); Assert.NotNull(savedCompany); Assert.Equal(testCompany.Name, savedCompany.Name); } }
如果需要测试模型校验失败的场景,可在调用Create方法前手动给ModelState注入错误:
controller.ModelState.AddModelError("Name", "公司名称为必填项");
Index 方法单元测试示例
测试逻辑覆盖:分页查询公司列表时,正确调用分页服务并返回对应数据
[Fact] public async Task Index_Get_ReturnsPaginatedCompanyList() { // 初始化依赖 var mockPageService = new Mock<IPaginatedListService>(); var testDbContext = GetTestDbContext(); // 预置测试数据到内存库 var testCompanies = new List<Company> { new Company{ Id = 1, Name = "公司A"}, new Company{ Id = 2, Name = "公司B"}, new Company{ Id = 3, Name = "公司C"} }; await testDbContext.Companies.AddRangeAsync(testCompanies); await testDbContext.SaveChangesAsync(); // 配置Mock分页服务的返回值 var expectedPageData = new PaginatedList<Company>(testCompanies, 3, 1, 2); mockPageService .Setup(s => s.CreateAsync(It.IsAny<IQueryable<Company>>(), It.IsAny<int>(), It.IsAny<int>())) .ReturnsAsync(expectedPageData); var controller = new CompanyController(testDbContext, mockPageService.Object); // 执行测试方法,查询第1页,每页2条 var result = await controller.Index(pageNumber: 1, pageSize: 2); // 断言逻辑 var viewRes = Assert.IsType<ViewResult>(result); var resultData = Assert.IsType<PaginatedList<Company>>(viewRes.Model); Assert.Equal(3, resultData.TotalCount); Assert.Equal(1, resultData.PageIndex); // 验证分页服务被正确调用一次 mockPageService.Verify(s => s.CreateAsync(It.IsAny<IQueryable<Company>>(), 1, 2), Times.Once); }
入门学习建议
- 先从无业务依赖的简单方法入手,熟悉xUnit的基础断言语法
- 掌握Moq的核心用法:接口模拟、返回值配置、调用次数验证
- 明确EF Core内存数据库的适用场景,区分复杂查询场景下Mock上下文和使用内存库的差异
内容的提问来源于stack exchange,提问作者Marco Leone
相关产品推荐
相关产品推荐

