千家信息网

ASP.NET Core项目如何使用xUnit进行单元测试

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,小编给大家分享一下ASP.NET Core项目如何使用xUnit进行单元测试,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!
千家信息网最后更新 2026年11月04日ASP.NET Core项目如何使用xUnit进行单元测试

小编给大家分享一下ASP.NET Core项目如何使用xUnit进行单元测试,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

一、前言

在以前的.NET Framework项目中,我们也写过一些单元测试的项目,而在ASP.NET Core 这种Web或者API应用程序中要做单元测试是很方便的。

这篇文章主要讲解如何使用xUnit对ASP.NET Core应用程序做单元测试。.NET Core中常用的测试工具还有NUnit和MSTest。

xUnit是一个测试框架,可以针对.net/.net core项目进行测试。测试项目需要引用被测试的项目,从而对其进行测试。测试项目同时需要引用xUnit库。测试编写好后,用Test Runner来运行测试。Test Runner可以读取测试代码,并且会知道我们所使用的测试框架,然后执行,并显示结果。目前可用的Test Runner包括vs自带的Test Explorer,或者dotnet core命令行,以及第三方工具,例如resharper等。

xUnit可以支持多种平台的测试:

  • .NET Framework

  • .NET Core

  • .NET Standard

  • UWP

  • Xamarin

二、创建示例项目

为了使示例项目更加的贴近真实的项目开发,这里采用分层的方式创建一个示例项目,创建完成后的项目结构如下图所示:

下面讲解一下每层的作用,按照从上往下的顺序:

  • TestDemo:从名字就可以看出来,这是一个单元测试的项目,针对控制器进行测试。

  • UnitTest.Data:数据访问,封装与EntityFrameworkCore相关的操作。

  • UnitTest.IRepository:泛型仓储接口,封装基础的增删改查。

  • UnitTest.Model:实体层,定义项目中使用到的所有实体。

  • UnitTest.Repository:泛型仓储接口实现层,实现接口里面定义的方法。

  • UnitTestDemo:ASP.NET Core WebApi,提供API接口。

1、UnitTest.Model

实体层里面只有一个Student类:

using System;using System.Collections.Generic;using System.Text;namespace UnitTest.Model{    public class Student    {        public int ID { get; set; }        public string Name { get; set; }        public int Age { get; set; }        public string Gender { get; set; }    }}

2、UnitTest.Data

里面封装与EF Core有关的操作,首先需要引入Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools三个NuGet包,直接在管理NuGet程序包里面引入,这里不在讲述。

引入相关NuGet包以后,我们创建数据上下文类,该类继承自EF Core的DbContext,里面设置表名和一些属性:

using Microsoft.EntityFrameworkCore;using UnitTest.Model;namespace UnitTest.Data{    ///     /// 数据上下文类    ///     public class AppDbContext : DbContext    {        ///         /// 通过构造函数给父类构造传参        ///         ///         public AppDbContext(DbContextOptions options) : base(options)        {        }        public DbSet Students { get; set; }        protected override void OnModelCreating(ModelBuilder modelBuilder)        {            modelBuilder.Entity().ToTable("T_Student");            modelBuilder.Entity().HasKey(p => p.ID);            modelBuilder.Entity().Property(p => p.Name).HasMaxLength(32);            // 添加种子数据            modelBuilder.Entity().HasData(                new Student()                {                    ID = 1,                    Name = "测试1",                    Age = 20,                    Gender = "男"                },                new Student()                {                    ID = 2,                    Name = "测试2",                    Age = 22,                    Gender = "女"                },                new Student()                {                    ID = 3,                    Name = "测试3",                    Age = 23,                    Gender = "男"                });            base.OnModelCreating(modelBuilder);        }    }}

这里采用数据迁移的方式生成数据库,需要在API项目中引入Microsoft.EntityFrameworkCore、Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools三个NuGet包。引入方式同上。

然后在API项目的appsettings.json文件里面添加数据库链接字符串:

{  "Logging": {    "LogLevel": {      "Default": "Information",      "Microsoft": "Warning",      "Microsoft.Hosting.Lifetime": "Information"    }  },  "AllowedHosts": "*",  // 数据库连接字符串  "ConnectionString": {    "DbConnection": "Initial Catalog=TestDb;User Id=sa;Password=1234;Data Source=.;Connection Timeout=10;"  }}

在JSON文件中添加完连接字符串以后,修改Startup类的ConfigureServices方法,在里面配置使用在json文件中添加的连接字符串:

// 添加数据库连接字符串services.AddDbContext(options => {    options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("DbConnection").Value);});

这样就可以使用数据迁移的方式生成数据库了。

3、UnitTest.IRepository

该项目中使用泛型仓储,定义一个泛型仓储接口:

using System.Collections.Generic;using System.Threading.Tasks;namespace UnitTest.IRepository{    public interface IRepository where T:class,new()    {        Task> GetList();        Task Add(T entity);        Task Update(T entity);        Task Delete(T entity);    }}

然后在定义IStudentRepository接口继承自IRepository泛型接口:

using UnitTest.Model;namespace UnitTest.IRepository{    public interface IStudentRepository: IRepository    {    }}

4、UnitTest.Repository

这里是实现上面定义的仓储接口:

using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using UnitTest.Data;using UnitTest.IRepository;using UnitTest.Model;namespace UnitTest.Repository{    public class StudentRepository : IStudentRepository    {        private readonly AppDbContext _dbContext;        ///         /// 通过构造函数实现依赖注入        ///         ///         public StudentRepository(AppDbContext dbContext)        {            _dbContext = dbContext;        }        public async Task Add(Student entity)        {            _dbContext.Students.Add(entity);            return await _dbContext.SaveChangesAsync();        }        public async Task Delete(Student entity)        {            _dbContext.Students.Remove(entity);            return await _dbContext.SaveChangesAsync();        }        public async Task> GetList()        {            List list = new List();            list = await Task.Run>(() =>             {                return _dbContext.Students.ToList();            });                      return list;        }        public async Task Update(Student entity)        {            Student student = _dbContext.Students.Find(entity.ID);            if (student != null)            {                student.Name = entity.Name;                student.Age = entity.Age;                student.Gender = entity.Gender;                _dbContext.Entry(student).State = Microsoft.EntityFrameworkCore.EntityState.Modified;                return await _dbContext.SaveChangesAsync();            }            return 0;        }    }}

5、UnitTestDemo

先添加一个Value控制器,里面只有一个Get方法,而且没有任何的依赖关系,先进行最简单的测试:

using Microsoft.AspNetCore.Mvc;namespace UnitTestDemo.Controllers{    [Route("api/[controller]")]    [ApiController]    public class ValueController : ControllerBase    {        [HttpGet("{id}")]        public ActionResult Get(int id)        {            return $"Para is {id}";        }    }}

6、TestDemo

我们在添加测试项目的时候,直接选择使用xUnit测试项目,如下图所示:

这样项目创建完成以后,就会自动添加xUnit的引用:

                

但要测试 ASP.NET Core 应用还需要添加两个 NuGet 包:

Install-Package Microsoft.AspNetCore.AppInstall-Package Microsoft.AspNetCore.TestHost

上面是使用命令的方式进行安装,也可以在管理NuGet程序包里面进行搜索,然后安装。

千万不要忘记还要引入要测试的项目。最后的项目引入是这样的:

      netcoreapp3.1    false                                              

都添加完以后,重新编译项目,保证生成没有错误。

三、编写单元测试

单元测试按照从上往下的顺序,一般分为三个阶段:

  • Arrange:准备阶段。这个阶段做一些准备工作,例如创建对象实例,初始化数据等。

  • Act:行为阶段。这个阶段是用准备好的数据去调用要测试的方法。

  • Assert:断定阶段。这个阶段就是把调用目标方法的返回值和预期的值进行比较,如果和预期值一致则测试通过,否则测试失败。

我们在API项目中添加了一个Value控制器,我们以Get方法作为测试目标。一般一个单元测试方法就是一个测试用例。

我们在测试项目中添加一个ValueTest测试类,然后编写一个单元测试方法,这里是采用模拟HTTPClient发送Http请求的方式进行测试:

using Microsoft.AspNetCore;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using System.Net;using System.Net.Http;using System.Threading.Tasks;using UnitTestDemo;using Xunit;namespace TestDemo{    public class ValueTests    {        public HttpClient _client { get; }        ///         /// 构造方法        ///         public ValueTests()        {            var server = new TestServer(WebHost.CreateDefaultBuilder()           .UseStartup());            _client = server.CreateClient();        }        [Fact]        public async Task GetById_ShouldBe_Ok()        {            // 1、Arrange            var id = 1;            // 2、Act            // 调用异步的Get方法            var response = await _client.GetAsync($"/api/value/{id}");            // 3、Assert            Assert.Equal(HttpStatusCode.OK, response.StatusCode);        }    }}

我们在构造函数中,通过TestServer拿到一个HttpClient对象,用它来模拟Http请求。我们写了一个测试用例,完整演示了单元测试的Arrange、Act和Assert三个步骤。

1、运行单元测试

单元测试用例写好以后,打开"测试资源管理器":

在底部就可以看到测试资源管理器了:

在要测试的方法上面右键,选择"运行测试"就可以进行测试了:

注意观察测试方法前面图标的颜色,目前是蓝色的,表示测试用例还没有运行过:

测试用例结束以后,我们在测试资源管理器里面可以看到结果:

绿色表示测试通过。我们还可以看到执行测试用例消耗的时间。

如果测试结果和预期结果一致,那么测试用例前面图标的颜色也会变成绿色:

如果测试结果和预期结果不一致就会显示红色,然后需要修改代码直到出现绿色图标。我们修改测试用例,模拟测试失败的情况:

using Microsoft.AspNetCore;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using System.Net;using System.Net.Http;using System.Threading.Tasks;using UnitTestDemo;using Xunit;namespace TestDemo{    public class ValueTests    {        public HttpClient _client { get; }        ///         /// 构造方法        ///         public ValueTests()        {            var server = new TestServer(WebHost.CreateDefaultBuilder()           .UseStartup());            _client = server.CreateClient();        }        [Fact]        public async Task GetById_ShouldBe_Ok()        {            // 1、Arrange            var id = 1;            // 2、Act            // 调用异步的Get方法            var response = await _client.GetAsync($"/api/value/{id}");            //// 3、Assert            //Assert.Equal(HttpStatusCode.OK, response.StatusCode);            // 3、Assert            // 模拟测试失败            Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);        }    }}

然后运行测试用例:

2、调试单元测试

我们也可以通过添加断点的方式在测试用例中进行调试。调试单元测试很简单,只需要在要调试的方法上面右键选择"调试测试",如下图所示:

其它操作就跟调试普通方法一样。

除了添加断点调试,我们还可以采用打印日志的方法来快速调试,xUnit可以很方便地做到这一点。我们修改ValueTest类:

using Microsoft.AspNetCore;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using System.Net;using System.Net.Http;using System.Threading.Tasks;using UnitTestDemo;using Xunit;using Xunit.Abstractions;namespace TestDemo{    public class ValueTests    {        public HttpClient _client { get; }        public ITestOutputHelper Output { get; }        ///         /// 构造方法        ///         public ValueTests(ITestOutputHelper outputHelper)        {            var server = new TestServer(WebHost.CreateDefaultBuilder()           .UseStartup());            _client = server.CreateClient();            Output = outputHelper;        }        [Fact]        public async Task GetById_ShouldBe_Ok()        {            // 1、Arrange            var id = 1;            // 2、Act            // 调用异步的Get方法            var response = await _client.GetAsync($"/api/value/{id}");            // 3、Assert            // 模拟测试失败            //Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);            // 输出返回信息            // Output            var responseText = await response.Content.ReadAsStringAsync();            Output.WriteLine(responseText);            // 3、Assert            Assert.Equal(HttpStatusCode.OK, response.StatusCode);        }    }}

这里我们在构造函数中添加了 ITestOutputHelper 参数,xUnit 会将一个实现此接口的实例注入进来。拿到这个实例后,我们就可以用它来输出日志了。运行(注意不是 Debug)此方法,运行结束后在测试资源管理器里面查看:

点击就可以看到输出的日志了:

在上面的例子中,我们是使用的简单的Value控制器进行测试,控制器里面没有其他依赖关系,如果控制器里面有依赖关系该如何测试呢?方法还是一样的,我们新建一个Student控制器,里面依赖IStudentRepository接口,代码如下:

using System.Collections.Generic;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc;using UnitTest.IRepository;using UnitTest.Model;namespace UnitTestDemo.Controllers{    [Route("api/student")]    [ApiController]    public class StudentController : ControllerBase    {        private readonly IStudentRepository _repository;        ///         /// 通过构造函数注入        ///         ///         public StudentController(IStudentRepository repository)        {            _repository = repository;        }        ///         /// get方法        ///         ///         [HttpGet]        public async Task>> Get()        {            return await _repository.GetList();        }    }}

然后在Startup类的ConfigureServices方法中注入:

public void ConfigureServices(IServiceCollection services){    // 添加数据库连接字符串    services.AddDbContext(options =>     {        options.UseSqlServer(Configuration.GetSection("ConnectionString").GetSection("DbConnection").Value);    });    // 添加依赖注入到容器中    services.AddScoped();    services.AddControllers();}

在单元测试项目中添加StudentTest类:

using Microsoft.AspNetCore;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using Newtonsoft.Json;using System.Collections.Generic;using System.Net.Http;using System.Threading.Tasks;using UnitTest.Model;using UnitTestDemo;using Xunit;using Xunit.Abstractions;namespace TestDemo{    public class StudentTest    {        public HttpClient Client { get; }        public ITestOutputHelper Output { get; }        public StudentTest(ITestOutputHelper outputHelper)        {            var server = new TestServer(WebHost.CreateDefaultBuilder()           .UseStartup());            Client = server.CreateClient();            Output = outputHelper;        }        [Fact]        public async Task Get_ShouldBe_Ok()        {            // 2、Act            var response = await Client.GetAsync($"api/student");            // Output            string context = await response.Content.ReadAsStringAsync();            Output.WriteLine(context);            List list = JsonConvert.DeserializeObject>(context);            // Assert            Assert.Equal(3, list.Count);        }    }}

然后运行单元测试:

可以看到,控制器里面如果有依赖关系,也是可以使用这种方式进行测试的。

Post方法也可以使用同样的方式进行测试,修改控制器,添加Post方法:

using System.Collections.Generic;using System.Threading.Tasks;using Microsoft.AspNetCore.Mvc;using UnitTest.IRepository;using UnitTest.Model;namespace UnitTestDemo.Controllers{    [Route("api/student")]    [ApiController]    public class StudentController : ControllerBase    {        private readonly IStudentRepository _repository;        ///         /// 通过构造函数注入        ///         ///         public StudentController(IStudentRepository repository)        {            _repository = repository;        }        ///         /// get方法        ///         ///         [HttpGet]        public async Task>> Get()        {            return await _repository.GetList();        }        ///         /// Post方法        ///         ///         ///         [HttpPost]        public async Task Post([FromBody]Student entity)        {            int? result = await _repository.Add(entity);            if(result==null)            {                return false;            }            else            {                return result > 0 ? true : false;            }                    }    }}

在增加一个Post的测试方法:

using Microsoft.AspNetCore;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using Newtonsoft.Json;using System.Collections.Generic;using System.Net.Http;using System.Threading.Tasks;using UnitTest.Model;using UnitTestDemo;using Xunit;using Xunit.Abstractions;namespace TestDemo{    public class StudentTest    {        public HttpClient Client { get; }        public ITestOutputHelper Output { get; }        public StudentTest(ITestOutputHelper outputHelper)        {            var server = new TestServer(WebHost.CreateDefaultBuilder()           .UseStartup());            Client = server.CreateClient();            Output = outputHelper;        }        [Fact]        public async Task Get_ShouldBe_Ok()        {            // 2、Act            var response = await Client.GetAsync($"api/student");            // Output            string context = await response.Content.ReadAsStringAsync();            Output.WriteLine(context);            List list = JsonConvert.DeserializeObject>(context);            // Assert            Assert.Equal(3, list.Count);        }        [Fact]        public async Task Post_ShouldBe_Ok()        {            // 1、Arrange            Student entity = new Student()            {             Name="测试9",             Age=25,             Gender="男"            };            var str = JsonConvert.SerializeObject(entity);            HttpContent content = new StringContent(str);            // 2、Act            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");            HttpResponseMessage response = await Client.PostAsync("api/student", content);            string responseBody = await response.Content.ReadAsStringAsync();            Output.WriteLine(responseBody);            // 3、Assert            Assert.Equal("true", responseBody);        }    }}

运行测试用例:

这样一个简单的单元测试就完成了。

我们观察上面的两个测试类,发现这两个类都有一个共同的特点:都是在构造函数里面创建一个HttpClient对象,我们可以把创建HttpClient对象抽离到一个共同的基类里面,所有的类都继承自基类。该基类代码如下:

using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using System.IO;using System.Net.Http;using UnitTestDemo;namespace TestDemo{    ///     /// 基类    ///     public class ApiControllerTestBase    {        ///         /// 返回HttpClient对象        ///         ///         protected HttpClient GetClient()        {            var builder = new WebHostBuilder()                                // 指定使用当前目录                                .UseContentRoot(Directory.GetCurrentDirectory())                                // 使用Startup类作为启动类                                .UseStartup()                                // 设置使用测试环境                                .UseEnvironment("Testing");            var server = new TestServer(builder);            // 创建HttpClient            HttpClient client = server.CreateClient();            return client;        }    }}

然后修改StudentTest类,使该类继承自上面创建的基类:

using Newtonsoft.Json;using System.Collections.Generic;using System.Net.Http;using System.Threading.Tasks;using UnitTest.Model;using Xunit;using Xunit.Abstractions;namespace TestDemo{    public class StudentTest: ApiControllerTestBase    {        public HttpClient Client { get; }        public ITestOutputHelper Output { get; }        public StudentTest(ITestOutputHelper outputHelper)        {            // var server = new TestServer(WebHost.CreateDefaultBuilder()            //.UseStartup());            // Client = server.CreateClient();            // 从父类里面获取HttpClient对象            Client = base.GetClient();            Output = outputHelper;        }        [Fact]        public async Task Get_ShouldBe_Ok()        {            // 2、Act            var response = await Client.GetAsync($"api/student");            // Output            string context = await response.Content.ReadAsStringAsync();            Output.WriteLine(context);            List list = JsonConvert.DeserializeObject>(context);            // Assert            Assert.Equal(3, list.Count);        }        [Fact]        public async Task Post_ShouldBe_Ok()        {            // 1、Arrange            Student entity = new Student()            {             Name="测试9",             Age=25,             Gender="男"            };            var str = JsonConvert.SerializeObject(entity);            HttpContent content = new StringContent(str);            // 2、Act            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");            HttpResponseMessage response = await Client.PostAsync("api/student", content);            string responseBody = await response.Content.ReadAsStringAsync();            Output.WriteLine(responseBody);            // 3、Assert            Assert.Equal("true", responseBody);        }    }}

以上是"ASP.NET Core项目如何使用xUnit进行单元测试"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

测试 项目 方法 单元 数据 接口 控制器 方式 控制 运行 函数 阶段 字符 字符串 对象 数据库 结果 管理 仓储 三个 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 百单网互联网科技有限公司 互联网科技公司经营范围 国智互联网科技 圆梦互联网科技 科技互联网公司装修 科技互联网技术 有马互联网科技 睿民互联网科技 西藏七维空间互联网科技有限公司 顶级互联网科技成果有哪些 苏州五指互联网络科技有限 玉溪互联网科技怎么选 翼展科技 首家互联网医院 深圳艾果互联网通讯科技 互联网数据科技专业 互联网金融科技公司的优势 互联网科技包装股份有限公司上市 全球科技与互联网公司市值变化 北京天晋互联网科技有限公司 重庆科技产业互联网有哪些 西安纸贵互联网科技有限公司 互联网品科技 广州互联网科技公司 兼蜜互联网科技 互联网科技股午盘股票涨跌 互联网金融科技公司干啥的 崇明区互联网教育科技诚信经营 智帮互联网科技公司最近动向 艺电能源互联网科技有限公司招聘 中国科技和美国互联网
0