xUnit
This time I used thexUnit
testing framework, rather than the one that comes with VSMSTest
Frame. 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 theswagger
Going 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.
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.
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 methodaction
of the unit tests. But in general, the controller andService
layer will inject many services, and theactiondependencies on these services. How are unit tests going to handle this situation when using dependency injection?
compatible with core
It 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 thenew
operator 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 aHomeControllerUnitTest
class 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 classAction
of 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.