In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-22 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
Editor to share with you how to use xUnit for unit testing of the ASP.NET Core project, I believe most people do not know much about it, so share this article for your reference, I hope you can learn a lot after reading this article, let's go to know it!
I. Preface
In the previous NET Framework project, we also wrote some unit testing projects, and it is very convenient to do unit testing in Web or API applications such as ASP.NET Core.
This article focuses on how to use xUnit to unit test ASP.NET Core applications. Other common testing tools in .NET Core are NUnit and MSTest.
XUnit is a testing framework that can be tested against. Net /. Net core projects. The test project needs to reference the project being tested in order to test it. The test project also needs to reference the xUnit library. After the test is written, run the test with Test Runner. Test Runner can read the test code, and will know the test framework we are using, then execute it, and display the results. Currently available Test Runner includes Test Explorer that comes with vs, or dotnet core command line, as well as third-party tools, such as resharper.
XUnit can support testing on multiple platforms:
.NET Framework
.NET Core
.NET Standard
UWP
Xamarin
Second, create a sample project
In order to make the sample project closer to the real project development, a sample project is created in a hierarchical manner. The project structure after creation is shown in the following figure:
Let's explain the function of each layer, in the order from top to bottom:
TestDemo: as you can tell from the name, this is a unit test project that tests against the controller.
UnitTest.Data: data access that encapsulates EntityFrameworkCore-related operations.
UnitTest.IRepository: generic warehousing interface that encapsulates basic additions, deletions, modifications and queries.
UnitTest.Model: the entity layer that defines all entities used in the project.
UnitTest.Repository: the generic warehousing interface implementation layer, which implements the methods defined in the interface.
UnitTestDemo:ASP.NET Core WebApi, which provides API interface.
1 、 UnitTest.Model
There is only one Student class in the physical layer:
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
To encapsulate the operations related to EF Core, you first need to introduce three NuGet packages: Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Tools, which are directly introduced in the management NuGet package, which is not described here.
After introducing the relevant NuGet package, we create a data context class, which inherits from EF Core's DbContext, and sets the table name and some properties:
Using Microsoft.EntityFrameworkCore;using UnitTest.Model;namespace UnitTest.Data {/ data context class / public class AppDbContext: DbContext {/ pass parameters / public AppDbContext (DbContextOptions options): base (options) {} public DbSet Students {get; set to the parent constructor through the constructor. } 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) / / add seed data modelBuilder.Entity () .HasData (new Student () {ID = 1, Name = "Test 1", Age = 20, Gender = "male"} New Student () {ID = 2, Name = "Test 2", Age = 22, Gender = "female"}, new Student () {ID = 3 Name = "Test 3", Age = 23, Gender = "male"}) Base.OnModelCreating (modelBuilder);}
Here, the database is generated by data migration, and three NuGet packages of Microsoft.EntityFrameworkCore, Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Tools need to be introduced into the API project. The introduction method is the same as above.
Then add the database link string to the appsettings.json file of the API project:
{"Logging": {"LogLevel": {"Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information"}, "AllowedHosts": "*", / / database connection string "ConnectionString": {"DbConnection": "Initial Catalog=TestDb;User Id=sa;Password=1234;Data Source=.;Connection Timeout=10;"}}
After adding the connection string in the JSON file, modify the ConfigureServices method of the Startup class to configure to use the connection string added in the json file:
/ / add the database connection string services.AddDbContext (options = > {options.UseSqlServer (Configuration.GetSection ("ConnectionString") .GetSection ("DbConnection") .value);})
In this way, the database can be generated using data migration.
3 、 UnitTest.IRepository
The project uses generic warehousing to define a generic warehousing interface:
Using System.Collections.Generic;using System.Threading.Tasks;namespace UnitTest.IRepository {public interface IRepository where Task GetList (); Task Add (T entity); Task Update (T entity); Task Delete (T entity);}}
Then after defining the IStudentRepository interface inherits from the IRepository generic interface:
Using UnitTest.Model;namespace UnitTest.IRepository {public interface IStudentRepository: IRepository {}} 4 、 UnitTest.Repository
Here is the implementation of the warehouse interface defined above:
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; / dependency injection / public StudentRepository (AppDbContext dbContext) {_ dbContext = dbContext through constructor } 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
First add a Value controller, there is only one Get method, and do not have any dependencies, first do the simplest test:
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
When we add a test project, we directly choose to use the xUnit test project, as shown in the following figure:
In this way, when the project is created, a reference to xUnit is automatically added:
However, to test the ASP.NET Core application, you need to add two more NuGet packages:
Install-Package Microsoft.AspNetCore.AppInstall-Package Microsoft.AspNetCore.TestHost
The above is installed using the command, or you can search in the admin NuGet package and install it.
Don't forget to introduce the project to be tested. The final project introduction is as follows:
Netcoreapp3.1 false
After all has been added, recompile the project to ensure that there are no errors in the build.
Third, write unit tests
Unit testing is generally divided into three phases in order from top to bottom:
Arrange: preparation phase. Do some preparatory work at this stage, such as creating object instances, initializing data, and so on.
Act: behavior phase. This phase invokes the method to be tested with the prepared data.
Assert: determine the phase. This phase is to compare the return value of the calling target method with the expected value, and if it is consistent with the expected value, the test passes, otherwise the test fails.
We added a Value controller to the API project, and we took the Get method as the test target. Generally, a unit test method is a test case.
We add a ValueTest test class to the test project, and then write a unit test method, which is tested by simulating HTTPClient sending Http requests:
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 } / constructor / 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 / / call the asynchronous Get method var response = await _ client.GetAsync ($"/ api/value/ {id}") / / 3, Assert Assert.Equal (HttpStatusCode.OK, response.StatusCode);}
In the constructor, we get a HttpClient object through TestServer and use it to simulate the Http request. We wrote a test case that fully demonstrated the three steps of unit testing: Arrange, Act, and Assert.
1. Run the unit test
After the unit test case is written, open Test Explorer:
You can see the test explorer at the bottom:
Right-click the method you want to test, and select "run Test" to test:
Notice that the color of the icon in front of the test method is currently blue, indicating that the test case has not been run yet:
After the test case is over, we can see the results in Test Explorer:
Green indicates that the test passed. We can also see the time it takes to execute the test case.
If the test results are consistent with the expected results, the color of the icon in front of the test case also changes to green:
If the test results are not consistent with the expected results, it will be displayed in red, and then you need to modify the code until the green icon appears. We modify the test case to simulate a test failure:
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 } / constructor / 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 / / call the asynchronous Get method var response = await _ client.GetAsync ($"/ api/value/ {id}") / 3, Assert / / Assert.Equal (HttpStatusCode.OK, response.StatusCode); / / 3, Assert / / Simulation test failed Assert.Equal (HttpStatusCode.BadRequest, response.StatusCode);}
Then run the test case:
2. Debug unit test
We can also debug in the test case by adding breakpoints. Debugging a unit test is simple, just right-click on the method you want to debug and select Debug Test, as shown in the following figure:
Other operations are the same as debugging common methods.
In addition to adding breakpoint debugging, we can also print logs to debug quickly, which xUnit can easily do. We modify the ValueTest class:
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 } / Construction method / 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 / / call the asynchronous Get method var response = await _ client.GetAsync ($"/ api/value/ {id}") / / 3. Assert / / Simulation test failed / / Assert.Equal (HttpStatusCode.BadRequest, response.StatusCode); / / output return information / / Output var responseText = await response.Content.ReadAsStringAsync (); Output.WriteLine (responseText); / / 3, Assert Assert.Equal (HttpStatusCode.OK, response.StatusCode) }}}
Here we add the ITestOutputHelper parameter to the constructor, and xUnit will inject an instance that implements this interface. Once we get this instance, we can use it to output the log. Run (not Debug) this method, and check it in Test Explorer after running:
Click to see the output log:
In the above example, we are using a simple Value controller for testing. There are no other dependencies in the controller. How can we test if there are dependencies in the controller? The method is the same. Let's create a new Student controller, which relies on the IStudentRepository interface. The code is as follows:
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 is injected through the constructor } / get method / [HttpGet] public async Task Get () {return await _ repository.GetList ();}
Then inject into the ConfigureServices method of the Startup class:
Public void ConfigureServices (IServiceCollection services) {/ / add the database connection string services.AddDbContext (options = > {options.UseSqlServer (Configuration.GetSection ("ConnectionString"). GetSection ("DbConnection") .Value); / / add dependency injection to the container services.AddScoped (); services.AddControllers ();}
Add the StudentTest class to the unit test project:
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);}
Then run the unit test:
As you can see, if there is a dependency in the controller, it can also be tested in this way.
The Post method can also be tested in the same way, modifying the controller and adding the Post method:
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 is injected through the constructor } / get method / [HttpGet] public async Task Get () {return await _ repository.GetList () } / Post method / [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;}
Add a test method for 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= "Test 9", Age=25, Gender= "male"} 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);}
Run the test case:
Such a simple unit test is complete.
We look at the above two test classes and find that these two classes have one thing in common: both create a HttpClient object in the constructor, we can extract the creation of HttpClient objects into a common base class, and all classes inherit from the base class. The base class code is as follows:
Using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.TestHost;using System.IO;using System.Net.Http;using UnitTestDemo Namespace TestDemo {/ Base class / public class ApiControllerTestBase {/ returns the HttpClient object / protected HttpClient GetClient () {var builder = new WebHostBuilder () / / specifies that the current directory is used. .UseContentRoot (Directory.GetCurrentDirectory ()) / / uses the Startup class as the startup class. UseStartup () / / sets the use of the test environment .UseEnvironment ("Testing") Var server = new TestServer (builder); / / create HttpClient HttpClient client = server.CreateClient (); return client;}}
Then modify the StudentTest class so that it inherits from the base class created above:
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 (); / / get the HttpClient object Client = base.GetClient () from the parent class; 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= "Test 9", Age=25, Gender= "male"}; 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) The above is all the content of the article "how to use xUnit for unit testing of ASP.NET Core projects". Thank you for reading! I believe we all have a certain understanding, hope to share the content to help you, if you want to learn more knowledge, welcome to follow the industry information channel!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.