Compiled by xUnit ASP.NET Core unit testing method

Time:2020-7-27

Remember the. Net framework ASP.NET Webform? In those days, it was a disaster to do unit testing in the web tier. The. Net core learned a lesson and designed for testability ASP.NET It is also convenient to do unit testing for web or API applications like core. Interface oriented and dependency injection play an important role in this aspect.

This article will teach you how to use xUnit to ASP.NET The core application does unit testing. NUnit and mstest are common testing tools for. Net core. I am used to using xUnit as testing tool, so this article uses xUnit.

Create sample project

First use ASP.NET Core API template to build an application.

The template automatically creates a valuescontroller for us. To facilitate the demonstration, we only leave one of the get methods:


public class ValuesController : ControllerBase
{
 // GET api/values/5
 [HttpGet("{id}")]
 public ActionResult<string> Get(int id)
 {
  return "value";
 }
}

Then add a xUnit unit test project:

The template automatically adds xUnit reference for us:


<ItemGroup>
 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
 <PackageReference Include="xunit" Version="2.4.0" />
 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

But test it ASP.NET The core application also needs to add two nuget packages:

Install-Package Microsoft.AspNetCore.App

Install-Package Microsoft.AspNetCore.TestHost

Of course, the target project will also be introduced. The last reference is this:


<ItemGroup>
 <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.5" />
 <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
 <PackageReference Include="xunit" Version="2.4.0" />
 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>

<ItemGroup>
 <ProjectReference Include="..\WebApplication1\WebApplication1.csproj" />
</ItemGroup>

After adding the reference, compile it to make sure that the reference is OK.

Writing unit tests

There are generally three steps to write a unit test: range, act and assert.

ArrangeIt is the preparation stage, which is the preparatory work, such as simulating data, initializing objects, etc;

ActIt is the behavior phase, which uses the prepared data to call the method to be tested;

Assert In the assertion phase, the value returned by calling the target method is compared with the expected value. If it is consistent with the expected value, the test is passed, otherwise it is a failure.

According to this step, we will write a unit test method with the get method in valuescontroller as the test target. Generally, a unit test method is a test case.

We add a valuestests unit test class in the test project, and then write a unit test method. The code is as follows:


public class ValuesTests
{
 public ValuesTests()
 {
  var server = new TestServer(WebHost.CreateDefaultBuilder()
   .UseStartup<Startup>());
  Client = server.CreateClient();
 }

 public HttpClient Client { get; }

 [Fact]
 public async Task GetById_ShouldBe_Ok()
 {
  // Arrange
  var id = 1;

  // Act
  var response = await Client.GetAsync($"/api/values/{id}");

  // Assert
  Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 }
}

Here, we get an httpclient object through TestServer. With it, we can simulate HTTP requests. We have written a very simple test case, which fully demonstrates the three steps of unit test: range, act and assert.

The “what should be what” pattern is recommended for method names of unit tests. For example, getbyid above_ ShouldBe_ OK means that the result returned by calling getbyid API should be OK, so that you can see what your test case is for without too many comments.

Running unit tests

After the unit test case is written, open “test explore” (Chinese version vs can see Chinese), right-click on the test method and select “run nested tests”, or right-click in the method code block to select “run tests”:

Notice the color of the icon in front of the test method. It is blue at present, indicating that the test case has not been run.

After the execution of the test case, if the result is consistent with the expectation, the green icon will appear

If the running result is not consistent with the expected result, it will be a red icon, and then you need to modify the code until the green icon appears. You can see the execution time under “test explore” and the execution details in the output window.

If the icon turns red, it may change to green after a lot of operations. In Test Driven DevelopmentBRG (blue red green)That’s how terminology comes from.

Debug unit test

You can debug in unit tests by adding breakpoints. The method is very simple. Right click on the method to be debugged and select “debug nested tests”. The debugging is the same as that of the debugging.

If we want to see what the API returns, we can view the variable string value of the returned result by adding breakpoint debugging, but this method is not the best choice. For example, for the same API, I want to see what the results of 10 kinds of parameters return. It is very troublesome to view them through breakpoint debugging every time.

In addition to adding breakpoints to debug, there is also a way to print logs for quick debugging, which xUnit can easily do. To this end, we modify a valuestests

public ValuesTests(ITestOutputHelper outputHelper)
{
 var server = new TestServer(WebHost.CreateDefaultBuilder()
  .UseStartup<Startup>());
 Client = server.CreateClient();
 Output = outputHelper;
}

public ITestOutputHelper Output{ get; }

//... (omit other codes)

Here we add the itestutputhelper parameter to the constructor, and xUnit will inject an instance that implements this interface. After getting this instance, we can use it to output the log:


[Fact]
public async Task GetById_ShouldBe_Ok()
{
 // Arrange
 var id = 1;

 // Act
 var response = await Client.GetAsync($"/api/values/{id}");

 // Output
 var responseText = await response.Content.ReadAsStringAsync();
 Output.WriteLine(responseText);

 // Assert
 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

Run (note that it is not debug). After running, you can see the word “output” under “test explore”. Click it to see the output result, as shown in the figure below:

In this way, we can easily view the output results every time we run the test.

other

Above, we call API tests by simulating HTTP requests. There is another way to call the action method of new controller directly. For example:


// Arrange
var id = 1;
var controller = new ValuesController();
// Act
var result = controller.Get(id);

If the controller has no other dependencies, this method is of course the most convenient. However, the controller usually has one or more dependencies, such as:


public class ValuesController : Controller
{
 private readonly ISessionRepository _sessionRepository;

 public ValuesController(ISessionRepository sessionRepository)
 {
  _sessionRepository = sessionRepository;
 }

 // ...
}

We are going to simulate and instantiate all the dependencies of this controller. Of course, manual simulation is not realistic, because a dependency class may also rely on other classes or interfaces, and the dependency chain may be very long. You can’t instantiate each dependency manually. A tool called MOQ can automatically simulate instantiation dependency. Its usage is as follows:


// ..
// Arrange
var mockRepo = new Mock<ISessionRepository>();
mockRepo.Setup(...);
var controller = new HomeController(mockRepo.Object);

// Act
var result = await controller.Index();

I don’t recommend this method, because it is not as close to the real call scenario as simulating HTTP requests, regardless of the learning cost of MOQ. Therefore, this article will not introduce it too much. You can only know that there is such a thing.

That’s xUnit ASP.NET Core unit testing method details, more about xUnit writing ASP.NET For core unit testing information, please pay attention to other related articles in developeppaer!