Location>code7788 >text

Writing Unit Tests in WebAPI for the First Time

Popularity:500 ℃/2024-08-16 10:48:20

xUnit

This time I used thexUnittesting framework, rather than the one that comes with VSMSTestFrame. When adding a new project selectxUnit Test ProjectOn the line.

Only one difference has been experienced so far, thexUnitIt is convenient to pass parameters to the test method using features instead of having to define parameters one by one in an assignment statement in the test method.

One advantage of unit testing is that you can get failure information for many interfaces you are testing at once. If you use theswaggerGoing to test interfaces, you can only go to start the project, enter the password authentication, and then send requests one by one. It is more troublesome to deal with one interface when encountering an error. Unit testing can display the error messages of all interfaces in the test window, and it is cached, so you don't have to start the project, you just need to click the test button, and then all the interfaces are tested. When I was migrating interfaces, one controller migrated 14 interfaces at once, and the unit test passed 6 and failed 8, and the ones that failed were listed with error messages, which was comfortable. After a few days of using it, I found it so much easier than going to swagger or postman and manually testing the interfaces.

image

The failures here are basically caused by migrating the database structure, and as soon as I applied the structural changes, a few more passed the test. It looks pretty intuitive how the modifications are progressing. So far a couple days in, the tests still haven't all passed, and the unit tests are serving as a good monitor.

image

Clicking on a failed test shows you the call stack, jumping to the line of code that failed to run. This makes it easy to modify.

Unit test environment preparation

I wrote that in the controller methodactionof the unit tests. But in general, the controller andServicelayer will inject many services, and theactiondependencies on these services. How are unit tests going to handle this situation when using dependency injection?

compatible with coreIt does the same thing. It prepares a dependency injection container, then I prepare a dependency injection container.WebAPIA web host is also constructed. But the unit tests run independently, so there is no need to create a web host. Instead, you add aTestBase base class, which is used to create containers and register services for use by test methods.

//test environment
public class TestBase
{
	//Dependency Injection Container
	public IServiceCollection Services;
	//Getting Services from a Container
	public IServiceProvider Provider;

	public TestBase()
	{
		//Creating Containers
		Services = new ServiceCollection();
		//....Registration Services
		Provider = ();
	}
}

Then register the services we need with the container, such as the commonMemoryCache IWebHostEnvironment ISqlSugarClient XXXService. We wouldn't need to use thenewoperator to create service class instances, but instead can now be obtained directly from the container.

//Registered Service Layer
<ISingleWellService, SingleWellService>();
//Register Cache
<IMemoryCache, MemoryCache>(service =>
{
	return new MemoryCache(new MemoryCacheOptions());
});
//enrollmentSqlSuger
<ISqlSugarClient>(service =>
{
	return new SqlSugarClient(new ConnectionConfig()
	{
		ConnectionString = "Data Source=XXX",
		DbType = ,
		IsAutoCloseConnection = true,
		InitKeyType = ,
	});
});
//enrollment环境变量
<IWebHostEnvironment, WebHostEnvironment>();

IWebHostEnvironment

In order to use this interface you need to introduce the package. This interface is generallyWebApplicationBuildercreated, often used when it comes to reading and writing files. But there is no builder in the unit test project, so I added one myself.IWebHostEnvironmentimplementation class and register it with the container. The disadvantage is that you also have to copy files from the WebAPI project that you may need, such as template files and data files, to the unit test project as well.

public class WebHostEnvironment : IWebHostEnvironment
    {
        public string WebRootPath { get => (, "wwwroot");  }
        public IFileProvider WebRootFileProvider { get; set; }
        public string EnvironmentName { get; set; }
        public string ApplicationName { get; set; }
        public string ContentRootPath { get => ; }
        public IFileProvider ContentRootFileProvider { get; set; }
    }

ICurentUser

This custom interface is generally in the request processing pipeline to store the relevant information after authentication. Different interfaces in the unit test may require different users, such as a process, different roles call the same interface.ICurentUserThe same is registered to the container and then injected in the service layer. The specific business methods perform different logic depending on this role.

What I'm more confused about is how the unit tests should be injected again. It is important to note that the ICurentUser is different for different test methods. You should know that when we get the service from the container, the container automatically picks the appropriate constructor for us. But here, due to the difference in roles, we can't get the prepared role stubs directly from the container. Do we have to manually construct the service and pass it to the controller? I have heard thatmokq, but don't know how to use it to simulate multiple ICurentUsers.

Adding Unit Tests to a Controller

Add aHomeControllerUnitTestclass and inherits from the previously defined base classTestBase. We should take the appropriate service from the container for use in the constructor.

public class HomeControllerUnitTest:TestBase
    {
        HomeController homeController;
        public HomeControllerUnitTest()
        {
			//Injecting services from containers
            homeController = new HomeController(
				<IHomeService>(),
				<IWebHostEnvironment>());
        }
}

Next, add units to the test classActionof the test method. It's usually a three-step process

  • Prepare data Arrange
  • Calling Test Methods Act
  • Assert Result Assert

Also known as AAA mode.

[Theory(DisplayName = "beta (software)XXX")]
[InlineData("xxx", "xxx", 1,30)]
public void Test_GetData(string wId, string tId, int page, int rows)
{
	var data = (wId, tId, page, rows).Result;
	();
}

In practice, I would run the unrun tests for every test I added. The first day look at which tests failed, here it's usually the database structure that's not right, and then apply changes to the database. The next day run the failed tests again to verify. Will not run all tests every time.