{"id":643,"date":"2025-04-05T11:44:46","date_gmt":"2025-04-05T03:44:46","guid":{"rendered":"https:\/\/www.hyy.net\/?p=643"},"modified":"2025-04-05T11:44:46","modified_gmt":"2025-04-05T03:44:46","slug":"asp-net-core-in-action-36-testing-asp-net-core-applications","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=643","title":{"rendered":"ASP.NET Core in Action 36 Testing ASP.NET Core applications"},"content":{"rendered":"<p>36 Testing ASP.NET Core applications\u200c<\/p>\n<h2>This chapter covers<\/h2>\n<p>\u2022  Writing unit tests for custom middleware, API controllers, and minimal API endpoints<br \/>\n\u2022  Using the Test Host package to write integration tests Testing your real application\u2019s behavior with WebApplicationFactory<br \/>\n\u2022  Testing code dependent on Entity Framework Core with the in-memory database provider<\/p>\n<p>In chapter 35 I described how to test .NET 7 applications using the xUnit test project and the .NET Test software development kit (SDK). You learned how to create a test project, add a project reference to your application, and write unit tests for services in your app.<\/p>\n<p>In this chapter we focus on testing ASP.NET Core applications speci\ufb01cally. In sections 36.1 and 36.2 we\u2019ll look at how to test common features of your ASP.NET Core apps: custom middleware, API controllers, and minimal API endpoints. I show you how to write isolated unit tests for both, much like you would any other service, and I\u2019ll point out the tripping points to watch for.<\/p>\n<p>To ensure that components work correctly, it\u2019s important to test them in isolation. But you also need to test that they work correctly in a middleware pipeline. ASP.NET Core provides a handy Test Host package that lets you easily write these integration tests for your components. You can even go one step further with the WebApplicationFactory helper class and test that your app is working correctly. In section 36.3 you\u2019ll see how to use WebApplicationFactory to simulate requests to your application and verify that it generates the correct response.<\/p>\n<p>In the \ufb01nal section of this chapter I\u2019ll demonstrate how to use the SQLite database provider for Entity Framework Core (EF Core) with an in-memory database. You can use this provider to test services that depend on an EF Core DbContext without having to use a real database. That prevents the pain of having unknown database infrastructure and resetting the database between tests, with di\ufb00erent people having slightly di\ufb00erent database con\ufb01gurations.<\/p>\n<p>In chapter 35 I showed how to write unit tests for an exchange-rate calculator service, such as you might \ufb01nd in your application\u2019s domain model. If well designed, domain services are normally relatively easy to unit-test. But domain services only make up a portion of your application. It can also be useful to test your ASP.NET Core-speci\ufb01c constructs, such as custom middleware, as you\u2019ll see in the next section.<\/p>\n<h2>36.1 Unit testing custom middleware\u200c<\/h2>\n<p>In this section you\u2019ll learn how to test custom middleware in isolation. You\u2019ll see how to test whether your middleware handled a request or whether it called the next middleware in the pipeline. You\u2019ll also see how to read the response stream for your middleware.<\/p>\n<p>In chapter 31 you saw how to create custom middleware and encapsulate middleware as a class with an Invoke function. In this section you\u2019ll create unit tests for a simple health- check middleware component, similar to the one in chapter 31. This is a basic implementation, but it demonstrates the approach you can take for more complex middleware components.<\/p>\n<p>The middleware you\u2019ll be testing is shown in listing 36.1. When invoked, this middleware checks that the path starts with \/ping and, if it does, returns a plain text &quot;pong&quot; response. If the request doesn\u2019t match, it calls the next middleware in the pipeline (the provided RequestDelegate).<\/p>\n<p>Listing 36.1 StatusMiddleware to be tested, which returns a &quot;pong&quot; response<\/p>\n<pre><code>public class StatusMiddleware\n{\nprivate readonly RequestDelegate _next; \u2776\npublic StatusMiddleware(RequestDelegate next) \u2776\n{\n_next = next;\n}\npublic async Task Invoke(HttpContext context) \u2777\n{\nif(context.Request.Path.StartsWithSegments(&quot;\/ping&quot;)) \u2778\n{ \u2778\ncontext.Response.ContentType = &quot;text\/plain&quot;; \u2778\nawait context.Response.WriteAsync(&quot;pong&quot;); \u2778\nreturn; \u2778\n} \u2778\nawait _next(context); \u2779\n}\n}<\/code><\/pre>\n<p>\u2776 The RequestDelegate representing the rest of the middleware pipeline<br \/>\n\u2777 Called when the middleware is executed<br \/>\n\u2778 If the path starts with \u201c\/ping\u201d, a \u201cpong\u201d response is returned . . .<br \/>\n\u2779 . . . otherwise, the next middleware in the pipeline is invoked.<\/p>\n<p>In this section, you\u2019re going to test two simple cases: <\/p>\n<p>\u2022  When a request is made with a path of &quot;\/ping&quot;<br \/>\n\u2022  When a request is made with a di\ufb00erent path<\/p>\n<p><b>WARNING<\/b> Where possible, I recommend that you don\u2019t directly inspect paths in your middleware like this. A better approach is to use endpoint routing instead, as I discussed in chapter 31. The middleware in this section is for demonstration purposes only.<\/p>\n<p>Middleware is slightly complicated to unit-test because the HttpContext object is conceptually a big class. It contains all the details for the request and the response, which can mean there\u2019s a lot of surface area for your middleware to interact with. For that reason, I \ufb01nd unit tests tend to be tightly coupled to the middleware implementation, which is generally undesirable.<\/p>\n<p>For the \ufb01rst test, you\u2019ll look at the case where the incoming request Path doesn\u2019t start with \/ping. In this case,StatusMiddleware should leave the HttpContext unchanged and call the RequestDelegate provided in the constructor, which represents the next middleware in the pipeline.<\/p>\n<p>You could test this behavior in several ways, but in listing 36.2 you test that the RequestDelegate (essentially a one-parameter function) is executed by setting a local variable to true. In the Assert at the end of the method, you verify that the variable was set and therefore that the delegate was invoked. To invoke StatusMiddleware, create and pass in a DefaultHttpContext, which is an implementation of HttpContext.<\/p>\n<p><b>NOTE<\/b>  The DefaultHttpContext derives from HttpContext and is part of the base ASP.NET Core framework abstractions. If you\u2019re so inclined, you can explore the source code for it on GitHub at <a href=\"http:\/\/mng.bz\/MB9Q\">http:\/\/mng.bz\/MB9Q<\/a>.<\/p>\n<p>Listing 36.2 Unit testing StatusMiddleware when a nonmatching path is provided<\/p>\n<pre><code>[Fact]\npublic async Task ForNonMatchingRequest_CallsNextDelegate()\n{\nvar context = new DefaultHttpContext(); \u2776\ncontext.Request.Path = &quot;\/somethingelse&quot;; \u2776\nvar wasExecuted = false; \u2777\nRequestDelegate next = (HttpContext ctx) =&gt; \u2778\n{ \u2778\nwasExecuted = true; \u2778\nreturn Task.CompletedTask; \u2778\n}; \u2778\nvar middleware = new StatusMiddleware(next); \u2779\nawait middleware.Invoke(context); \u277a\nAssert.True(wasExecuted); \u277b\n}<\/code><\/pre>\n<p>\u2776 Creates a DefaultHttpContext and sets the path for the request<br \/>\n\u2777 Tracks whether the RequestDelegate was executed<br \/>\n\u2778 The RequestDelegate representing the next middleware should be invoked in<br \/>\nthis example.<br \/>\n\u2779 Creates an instance of the middleware, passing in the next RequestDelegate<br \/>\n\u277a Invokes the middleware with the HttpContext; should invoke the<br \/>\nRequestDelegate<br \/>\n\u277b Verifies that RequestDelegate was invoked<\/p>\n<p>When the middleware is invoked, it checks the provided Path and \ufb01nds that it doesn\u2019t match the required value of \/ping. The middleware therefore calls the next RequestDelegate and returns.<\/p>\n<p>The other obvious case to test is when the request Path is &quot;\/ping&quot;; the middleware should generate an appropriate response. You could test several characteristics of the response:<\/p>\n<p>\u2022  The response should have a 200 OK status code.<br \/>\n\u2022  The response should have a Content-Type of text\/plain.<br \/>\n\u2022  The response body should contain the &quot;pong&quot; string.<\/p>\n<p>Each of these characteristics represents a di\ufb00erent requirement, so you\u2019d typically codify each as a separate unit test. This makes it easier to tell exactly which requirement hasn\u2019t been met when a test fails. For simplicity, in listing 36.3 I show all these assertions in the same test.<\/p>\n<p>The positive case unit test is made more complex by the need to read the response body to con\ufb01rm it contains &quot;pong&quot;. DefaultHttpContext uses Stream.Null for the Response .Body object, which means anything written to Body is lost. To capture the response and read it out to verify the contents, you must replace the Body with a MemoryStream. After the middleware executes, you can use a StreamReader to read the contents of the MemoryStream into a string and verify it.<\/p>\n<p>Listing 36.3 Unit testing StatusMiddleware when a matching Path is provided<\/p>\n<pre><code>[Fact]\npublic async Task ReturnsPongBodyContent()\n{\nvar bodyStream = new MemoryStream(); \u2776\nvar context = new DefaultHttpContext(); \u2776\ncontext.Response.Body = bodyStream; \u2776\ncontext.Request.Path = &quot;\/ping&quot;; \u2777\nRequestDelegate next = (ctx) =&gt; Task.CompletedTask; \u2778\nvar middleware = new StatusMiddleware(next: next); \u2778\nawait middleware.Invoke(context); \u2779\nstring response; \u277a\nbodyStream.Seek(0, SeekOrigin.Begin); \u277a\nusing (var stringReader = new StreamReader(bodyStream)) \u277a\n{ \u277a\nresponse = await stringReader.ReadToEndAsync(); \u277a\n} \u277a\nAssert.Equal(&quot;pong&quot;, response); \u277b\nAssert.Equal(&quot;text\/plain&quot;, context.Response.ContentType); \u277c\nAssert.Equal(200, context.Response.StatusCode); \u277d\n}\n<\/code><\/pre>\n<p>\u2776 Creates a DefaultHttpContext and initializes the body with a MemoryStream<br \/>\n\u2777 The path is set to the required value for the StatusMiddleware.<br \/>\n\u2778 Creates an instance of the middleware and passes in a simple RequestDelegate<br \/>\n\u2779 Invokes the middleware<br \/>\n\u277a Rewinds the MemoryStream and reads the response body into a string<br \/>\n\u277b Verifies that the response has the correct value<br \/>\n\u277c Verifies that the ContentType response is correct<br \/>\n\u277d Verifies that the Status Code response is correct<\/p>\n<p>As you can see, unit testing middleware requires a lot of setup. On the positive side, it allows you to test your middleware in isolation, but in some cases, especially for simple middleware without any dependencies on databases or other services, integration testing can (somewhat surprisingly) be easier. In section 36.3 you\u2019ll create integration tests for this middleware to see the di\ufb00erence.<\/p>\n<p>Custom middleware is common in ASP.NET Core projects, but far more common are Razor Pages, API controllers, and minimal API endpoints. In the next section you\u2019ll see how you can unit test them in isolation from other components.<\/p>\n<h2>36.2 Unit testing API controllers and minimal API endpoints\u200c<\/h2>\n<p>In this section you\u2019ll learn how to unit-test API controllers and minimal API endpoints. You\u2019ll learn about the bene\ufb01ts and di\ufb03culties of testing these components in isolation and the situations when it can be useful.<\/p>\n<p>Unit tests are all about isolating behavior; you want to test only the logic contained in the component itself, separate from the behavior of any dependencies. The Razor Pages and MVC\/API frameworks use the \ufb01lter pipeline, routing, and model-binding systems, but these are all external to the controller or PageModels. The PageModels and controllers themselves are responsible for a limited number of things:<\/p>\n<p>\u2022  For invalid requests (that have failed validation, for example), return an appropriate ActionResult (API controllers) or redisplay a form (Razor Pages).<\/p>\n<p>\u2022  For valid requests, call the required business logic services and return an appropriate ActionResult (API controllers), or show or redirect to a success page (Razor Pages).<\/p>\n<p>\u2022  Optionally, apply resource-based authorization as required.<\/p>\n<p>Controllers and Razor Pages generally shouldn\u2019t contain business logic themselves; instead, they should call out to other services. Think of them more as orchestrators, serving as the intermediary between the HTTP interfaces your app exposes and your business logic services.<\/p>\n<p>If you follow this separation, you\u2019ll \ufb01nd it easier to write unit tests for your business logic, and you\u2019ll bene\ufb01t from greater \ufb02exibility when you want to change your controllers to meet your needs. With that in mind, there\u2019s often a drive to make your controllers and page handlers as thin as possible, to the point where there\u2019s not much left to test!<\/p>\n<p><b>TIP<\/b>  One of my \ufb01rst introductions to this idea was a series of posts by Jimmy Bogard. The following link points to the last post in the series, but it contains links to all the earlier posts too. Bogard is also behind the MediatR library (<a href=\"https:\/\/github.com\/jbogard\/MediatR\">https:\/\/github.com\/jbogard\/MediatR<\/a>), which makes creating thin controllers even easier. See \u201cPut your controllers on a diet: POSTs and commands\u201d: <a href=\"http:\/\/mng.bz\/7VNQ\">http:\/\/mng.bz\/7VNQ<\/a>.<\/p>\n<p>All that said, controllers and actions are classes and methods, so you can write unit tests for them. The di\ufb03culty is deciding what you want to test. As an example, we\u2019ll consider the simple API controller in the following listing, which converts a value using a provided exchange rate and returns a response.<\/p>\n<p>Listing 36.4 The API controller under test<\/p>\n<pre><code>[Route(&quot;api\/[controller]&quot;)]\npublic class CurrencyController : ControllerBase\n{\nprivate readonly CurrencyConverter _converter \u2776\n= new CurrencyConverter(); \u2776\n[HttpGet]\npublic ActionResult&lt;decimal&gt; Convert(InputModel model) \u2777\n{\nif (!ModelState.IsValid) \u2778\n{ \u2778\nreturn BadRequest(ModelState); \u2778\n} \u2778\ndecimal result = _converter.ConvertToGbp(model) \u2779\nreturn result; \u277a\n}\n}<\/code><\/pre>\n<p>\u2776 The CurrencyConverter would normally be injected using DI and is created here<br \/>\nfor simplicity.<br \/>\n\u2777 The Convert method returns an Action-Result<T>.<br \/>\n\u2778 If the input is invalid, returns a 400 Bad Request result, including the ModelState<br \/>\n\u2779 If the model is valid, calculates the result<br \/>\n\u277a Returns the result directly<\/p>\n<p>Let\u2019s \ufb01rst consider the happy path, when the controller receives a valid request. The following listing shows that you can create an instance of the API controller, call an action method, and receive an ActionResult<T> response.<\/p>\n<p>Listing 36.5 A simple API controller unit test<\/p>\n<pre><code>public class CurrencyControllerTest\n{\n[Fact]\npublic void Convert_ReturnsValue()\n{\nvar controller = new CurrencyController(); \u2776\nvar model = new InputModel \u2776\n{ \u2776\nValue = 1, \u2776\nExchangeRate = 3, \u2776\nDecimalPlaces = 2, \u2776\n}; \u2776\nActionResult&lt;decimal&gt; result = controller.Convert(model); \u2777\nAssert.NotNull(result); \u2778\n}\n}\n<\/code><\/pre>\n<p>\u2776 Creates an instance of the ConvertController to test and a model to send to the<br \/>\nAPI<br \/>\n\u2777 Invokes the ConvertToGbp method and captures the value returned<br \/>\n\u2778 Asserts that the IActionResult is not null<\/p>\n<p>An important point to note here is that you\u2019re testing only the return value of the action, the ActionResult<T>, not the response that\u2019s sent back to the user. The process of serializing the result to the response is handled by the Model-View-Controller (MVC) formatter infrastructure, as you saw in chapter 9, not by the controller.<\/p>\n<p>When you unit-test controllers, you\u2019re testing them separately from the MVC infrastructure, such as formatting, model binding, routing, and authentication. This is obviously by design, but as with testing middleware in section 36.1, it can make testing some aspects of your controller somewhat complex.<\/p>\n<p>Consider model validation. As you saw in chapter 6, one of the key responsibilities of action methods and Razor Page handlers is to check the ModelState.IsValid property and act accordingly if a binding model is invalid. Testing that your controllers and PageModels handle validation failures correctly seems like a good candidate for a unit test.<\/p>\n<p>Unfortunately, things aren\u2019t simple here either. The Razor Page\/MVC framework automatically sets the ModelState property as part of the model-binding process. In practice, when your action method or page handler is invoked in your running app, you know that the ModelState will match the binding model values. But in a unit test, there\u2019s no model binding, so you must set the ModelState yourself manually.<\/p>\n<p>Imagine you\u2019re interested in testing the error path for the controller in listing 36.4, where the model is invalid and the controller should return BadRequestObjectResult. In a unit test, you can\u2019t rely on the ModelState property being correct for the binding model. Instead, you must add a model-binding error to the controller\u2019s ModelState manually before calling the action, as shown in the following listing.<\/p>\n<p>Listing 36.6 Testing handling of validation errors in MVC controllers<\/p>\n<pre><code>[Fact]\npublic void Convert_ReturnsBadRequestWhenInvalid()\n{\nvar controller = new CurrencyController(); \u2776\nvar model = new ConvertInputModel \u2777\n{ \u2777\nValue = 1, \u2777\nExchangeRate = -2, \u2777\nDecimalPlaces = 2, \u2777\n}; \u2777\ncontroller.ModelState.AddModelError( \u2778\nnameof(model.ExchangeRate), \u2778\n&quot;Exchange rate must be greater than zero&quot; \u2778\n); \u2778\nActionResult&lt;decimal&gt; result = controller.Convert(model); \u2779\nAssert.IsType&lt;BadRequestObjectResult&gt;(result.Result); \u277a\n}<\/code><\/pre>\n<p>\u2776 Creates an instance of the Controller to test<br \/>\n\u2777 Creates an invalid binding model by using a negative ExchangeRate<br \/>\n\u2778 Manually adds a model error to the Controller\u2019s ModelState. This sets ModelState.IsValid to false.<br \/>\n\u2779 Invokes the action method, passing in the binding models<br \/>\n\u277a Verifies that the action method returned a BadRequestObjectResult<\/p>\n<p><b>NOTE<\/b>  In listing 36.6, I passed in an invalid model, but I could just as easily have passed in a valid model or even null; the controller doesn\u2019t use the binding model if the ModelState isn\u2019t valid, so the test would still pass. But if you\u2019re writing unit tests like this one, I recommend trying to keep your model consistent with your ModelState; otherwise, your unit tests won\u2019t be testing a situation that occurs in practice.<\/p>\n<p>I tend to shy away from unit testing API controllers directly in this way. As you\u2019ve seen with model binding, the controllers are somewhat dependent on earlier stages of the MVC framework, which you often need to emulate. Similarly, if your controllers access the HttpContext (available on the ControllerBase base classes), you may need to perform additional setup.<\/p>\n<p><b>NOTE<\/b>  You can read more about why I generally don\u2019t unit- test my controllers in my blog article \u201cShould you unit-test API\/MVC controllers in ASP.NET Core?\u201d at <a href=\"http:\/\/mng.bz\/YqMo\">http:\/\/mng.bz\/YqMo<\/a>.<\/p>\n<p>So what about minimal API endpoints? There\u2019s both good news and bad news here. On one hand, minimal API endpoints are simple lambda functions, so you can unit-test them, but these tests also su\ufb00er from many drawbacks:<\/p>\n<p>\u2022  You must write your endpoint handlers as static or instance methods on a class, not as lambda methods or local functions, so that you can reference them from the test project.<\/p>\n<p>\u2022  You are testing only the execution of the endpoint handler, outside any \ufb01lters applied to the endpoint or route group that execute in the real app.<\/p>\n<p>\u2022  You are not testing model-binding or result serialization\u2014two common sources of errors in practice.<\/p>\n<p>\u2022  If your endpoint is simple, as it should be, there\u2019s not much to test!<\/p>\n<p>I \ufb01nd unit tests for minimal APIs to be overly restrictive and limited in value, so I avoid them, but you can see an example of a minimal API unit test in the source code for this chapter.<\/p>\n<p><b>NOTE<\/b>  I haven\u2019t discussed Razor Pages much in this section, as they su\ufb00er from many of the same problems, in that they are dependent on the supporting infrastructure of the framework. Nevertheless, if you do wish to test your Razor Page PageModel, you can read about it in Microsoft\u2019s \u201cRazor Pages unit tests in ASP.NET Core\u201d documentation: <a href=\"http:\/\/mng.bz\/GxmM\">http:\/\/mng.bz\/GxmM<\/a>.<\/p>\n<p>Instead of using unit testing, I try to keep my minimal API endpoints, controllers, and Razor Pages as thin as possible. I push as much of the behavior in these classes into business logic services that can be easily unit-tested, or into middleware and \ufb01lters, which can be more easily tested independently.<\/p>\n<p><b>NOTE<\/b>  This is a personal preference. Some people like to get as close to 100 percent test coverage for their code base as possible, but I \ufb01nd testing orchestration classes is often more hassle than it\u2019s worth.<\/p>\n<p>Although I tend to forgo unit-testing my ASP.NET Core endpoints, I often write integration tests that test them in the context of a complete application. In the next section, we\u2019ll look at ways to write integration tests for your app so you can test its various components in the context of the ASP.NET Core framework as a whole.<\/p>\n<h2>36.3 Integration testing: Testing your whole app in-memory\u200c<\/h2>\n<p>In this section you\u2019ll learn how to create integration tests that test component interactions. You\u2019ll learn to create a TestServer that sends HTTP requests in-memory to test custom middleware components more easily. You\u2019ll then learn how to run integration tests for a real application, using your real app\u2019s con\ufb01guration, services, and middleware pipeline. Finally, you\u2019ll learn how to use WebApplicationFactory to replace services in your app with test versions to avoid depending on third-party APIs in your tests.<\/p>\n<p>If you search the internet for types of testing, you\u2019ll \ufb01nd a host of types to choose among. The di\ufb00erences are sometimes subtle, and people don\u2019t universally agree on the de\ufb01nitions. I chose not to dwell on that topic in this book. I consider unit tests to be isolated tests of a component and integration tests to be tests that exercise multiple components at the same time.<\/p>\n<p>In this section I\u2019m going to show how you can write integration tests for the StatusMiddleware from section 36.1 and the API controller from section 36.2. Instead of isolating the components from the surrounding framework and invoking them directly, you\u2019ll speci\ufb01cally test them in a context similar to how you use them in practice.<\/p>\n<p>Integration tests are an important part of con\ufb01rming that your components function correctly, but they don\u2019t remove the need for unit tests. Unit tests are excellent for testing small pieces of logic contained in your components and are typically quick to execute. Integration tests are normally signi\ufb01cantly slower, as they require much more con\ufb01guration and may rely on external infrastructure, such as a database.<\/p>\n<p>Consequently, it\u2019s normal to have far more unit tests for an app than integration tests. As you saw in chapter 35, unit tests typically verify the behavior of a component, using valid inputs, edge cases, and invalid inputs to ensure that the component behaves correctly in all cases. Once you have an extensive suite of unit tests, you\u2019ll likely need only a few integration tests to be con\ufb01dent your application is working correctly.<\/p>\n<p>You could write many types of integration tests for an application. You could test that a service can write to a database correctly, integrate with a third-party service (for sending emails, for example), or handle HTTP requests made to it.<\/p>\n<p>In this section we\u2019re going to focus on the last point: verifying that your app can handle requests made to it, as it would if you were accessing the app from a browser. For this, we\u2019re going to use a library provided by the ASP.NET Core team called Microsoft.AspNetCore.TestHost.<\/p>\n<h3>36.3.1 Creating a TestServer using the Test Host package\u200c<\/h3>\n<p>Imagine you want to write some integration tests for the StatusMiddleware from section 36.1. You\u2019ve already written unit tests for it, but you want to have at least one integration test that tests the middleware in the context of the ASP.NET Core infrastructure.<\/p>\n<p>You could go about this in many ways. Perhaps the most complete approach would be to create a separate project and con\ufb01gure StatusMiddleware as the only middleware in the pipeline. You\u2019d then need to run this project, wait for it to start up, send requests to it, and inspect the responses.<\/p>\n<p>This would possibly make for a good test, but it would also require a lot of con\ufb01guration, and it would be fragile and error-prone. What if the test app can\u2019t start because it tries to use an already-taken port? What if the test app doesn\u2019t shut down correctly? How long should the integration test wait for the app to start?<\/p>\n<p>The ASP.NET Core Test Host package lets you get close to this setup without having the added complexity of spinning up a separate app. You add the Test Host to your test project by adding the Microsoft.AspNetCore.TestHost NuGet package, using the Visual Studio NuGet GUI, Package Manager Console, or .NET command-line interface (CLI). Alternatively, add the <PackageReference> element directly to your test project\u2019s .csproj \ufb01le:\u200c<\/p>\n<pre><code>&lt;PackageReference Include=&quot;Microsoft.AspNetCore.TestHost&quot; Version=&quot;7.0.0&quot;\/&gt;<\/code><\/pre>\n<p>In a typical ASP.NET Core app, you create a HostBuilder in your Program class; con\ufb01gure a web server (Kestrel); and de\ufb01ne your application\u2019s con\ufb01guration, services, and middleware pipeline (using a Startup \ufb01le). Finally, you call Build() on the HostBuilder to create an instance of an IHost that can be run and that will listen for requests on a given URL and port.<\/p>\n<p><b>NOTE<\/b>  All this happens behind the scenes when you use the minimal hosting WebApplicationBuilder and WebApplication APIs. I have an in-depth post exploring the code behind WebApplicationBuilder and how it relates to HostBuilder on my blog at <a href=\"http:\/\/mng.bz\/a1mj\">http:\/\/mng.bz\/a1mj<\/a>.\u200c<\/p>\n<p>The Test Host package uses the same HostBuilder to de\ufb01ne your test application, but instead of listening for requests at the network level, it creates an IHost that uses in-memory request objects, as shown in \ufb01gure 36.1.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3601-1.jpg\" alt=\"alt text\" \/><br \/>\n<img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3601-2.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 36.1 When your app runs normally, it uses the Kestrel server. This listens for HTTP requests and converts the requests to an HttpContext, which is passed to the middleware pipeline. The TestServer doesn\u2019t listen for requests on the network. Instead, you use an HttpClient to make in-memory requests.From the point of view of the middleware, there\u2019s no di\ufb00erence.<\/p>\n<p>It even exposes an HttpClient that you can use to send requests to the test app. You can interact with the HttpClient as though it were sending requests over the network, but in reality, the requests are kept entirely in memory.<\/p>\n<p>Listing 36.7 shows how to use the Test Host package to create a simple integration test for the StatusMiddleware. First, create a HostBuilder, and call ConfigureWebHost() to de\ufb01ne your application by adding middleware in the Configure method. This is equivalent to the Startup.Configure() method you would typically use to con\ufb01gure your application when using the generic host approach.\u200c<\/p>\n<p><b>NOTE<\/b>  You can write a similar test using WebApplicationBuilder, but this sets up lots of extra defaults such as con\ufb01guration, extra dependency injection (DI) services, and automatically added middleware, which can generally slow and add some confusion to simple tests. You can see an example of this approach in StatusMiddlewareTestHostTests in the source code for this book, but I recommend using the approach in listing 36.7, using HostBuilder, in most cases.<\/p>\n<p>Call the UseTestServer() extension method in ConfigureWebHost(), which replaces the default Kestrel server with the TestServer from the Test Host package.<\/p>\n<p>The TestServer is the main component in the Test Host package, which makes all the magic possible. After con\ufb01guring the HostBuilder, call StartAsync() to build and start the test application. You can then create an HttpClient using the extension method GetTestClient(). This returns an HttpClient con\ufb01gured to make in-memory requests to the TestServer, as shown in the following listing.<\/p>\n<p>Listing 36.7 Creating an integration test with TestServer<\/p>\n<pre><code>\npublic class StatusMiddlewareTests\n{\n[Fact]\npublic async Task StatusMiddlewareReturnsPong()\n{\nvar hostBuilder = new HostBuilder() \u2776\n.ConfigureWebHost(webHost =&gt; \u2776\n{\nwebHost.Configure(app =&gt; \u2777\napp.UseMiddleware&lt;StatusMiddleware&gt;()); \u2777\nwebHost.UseTestServer(); \u2778\n});\nIHost host = await hostBuilder.StartAsync(); \u2779\nHttpClient client = host.GetTestClient(); \u277a\nvar response = await client.GetAsync(&quot;\/ping&quot;); \u277b\nresponse.EnsureSuccessStatusCode(); \u277c\nvar content = await response.Content.ReadAsStringAsync(); \u277d\nAssert.Equal(&quot;pong&quot;, content); \u277d\n}\n}<\/code><\/pre>\n<p>\u2776 Configures a HostBuilder to define the in-memory test app<br \/>\n\u2777 Adds the Status-Middleware as the only middleware in the pipeline<br \/>\n\u2778 Configures the host to use the TestServer instead of Kestrel<br \/>\n\u2779 Builds and starts the host<br \/>\n\u277a Creates an HttpClient, or you can interact directly with the server object<br \/>\n\u277b Makes an in-memory request, which is handled by the app as normal<br \/>\n\u277c Verifies that the response was a success (2xx) status code<br \/>\n\u277d Reads the body content and verifies that it contains \u201cpong\u201d<\/p>\n<p>This test ensures that the test application de\ufb01ned by HostBuilder returns the expected value when it receives a request to the \/ping path. The request is entirely in- memory, but from the point of view of StatusMiddleware, it\u2019s the same as if the request came from the network.<\/p>\n<p>The HostBuilder con\ufb01guration in this example is simple. Even though I\u2019ve called this an integration test, you\u2019re speci\ufb01cally testing the StatusMiddleware on its own rather than in the context of a real application. I think this setup is preferable for testing custom middleware compared with the \u201cproper\u201d unit tests I showed in section 36.1.<\/p>\n<p>Regardless of what you call it, this test relies on simple con\ufb01guration for the test app. You may also want to test the middleware in the context of your real application so that the result is representative of your app\u2019s real con\ufb01guration.<\/p>\n<p>If you want to run integration tests based on an existing app, you don\u2019t want to have to con\ufb01gure the test HostBuilder manually, as you did in listing 36.7. Instead, you can use another helper package, Microsoft.AspNetCore.Mvc.Testing.<\/p>\n<h3>36.3.2 Testing your application with WebApplicationFactory\u200c<\/h3>\n<p>Building up a HostBuilder and using the Test Host package, as you did in section 36.3.1, can be useful when you want to test isolated infrastructure components, such as middleware. However, it\u2019s also common to want to test your real app, with the full middleware pipeline con\ufb01gured and all the required services added to DI. This gives you the most con\ufb01dence that your application is going to work in production.<\/p>\n<p>The TestServer that provides the in-memory server can be used for testing your real app, but in principle, a lot more con\ufb01guration is required. Your real app likely loads con\ufb01guration \ufb01les or static \ufb01les; it may use Razor Pages and views, as well as using WebApplicationBuilder instead of the generic host. Fortunately, the Microsoft.AspNetCore.Mvc.Testing NuGet package and WebApplicationFactory largely solve these con\ufb01guration problems for you.<\/p>\n<p><b>NOTE<\/b>  Don\u2019t be put o\ufb00 by the Mvc in the package name; you can use this package for testing ASP.NET Core apps that don\u2019t use any MVC or Razor Pages services or components.<\/p>\n<p>You can use the WebApplicationFactory class (provided by the Microsoft.AspNetCore.Mvc.Testing NuGet package) to run an in-memory version of your real application. It uses the TestServer behind the scenes, but it uses your app\u2019s real con\ufb01guration, DI service registration, and middleware pipeline. The following listing shows an example that tests that when your application receives a &quot;\/ping&quot; request, it responds with &quot;pong&quot;.<\/p>\n<p>Listing 36.8 Creating an integration test with WebApplicationFactory<\/p>\n<pre><code>public class IntegrationTests: \u2776\nIClassFixture&lt;WebApplicationFactory&lt;Program&gt;&gt; \u2776\n{\nprivate readonly WebApplicationFactory&lt;Program&gt; _fixture; \u2777\npublic IntegrationTests( \u2777\nWebApplicationFactory&lt;Startup&gt; fixture) \u2777\n{ \u2777\n_fixture = fixture; \u2777\n} \u2777\n[Fact]\npublic async Task PingRequest_ReturnsPong()\n{\nHttpClient client = _fixture.CreateClient(); \u2778\nvar response = await client.GetAsync(&quot;\/ping&quot;); \u2779\nresponse.EnsureSuccessStatusCode(); \u2779\nvar content = await response.Content.ReadAsStringAsync(); \u2779\nAssert.Equal(&quot;pong&quot;, content); \u2779\n}\n}<\/code><\/pre>\n<p>\u2776 Implementing the interface allows sharing an instance across tests.<br \/>\n\u2777 Injects an instance of WebApplicationFactory<T>, where T is a class in your app<br \/>\n\u2778 Creates an HttpClient that sends requests to the in-memory TestServer<br \/>\n\u2779 Makes requests and verifies the response as before<\/p>\n<p>One of the advantages of using WebApplicationFactory as shown in listing 36.8 is that it requires less manual con\ufb01guration than using the TestServer directly, as shown in listing 36.13, despite performing more con\ufb01guration behind the scenes. The WebApplicationFactory tests your app using the con\ufb01guration de\ufb01ned in your Program.cs and Startup.cs \ufb01les.<\/p>\n<p><b>NOTE<\/b>  The generic WebApplicationFactory<T> must reference a public class in your app project. It\u2019s common to use the Program or Startup class. If you\u2019re using top-level statements for your app (the default in .NET 7), the automatically generated Program class is internal by default. To make it public and thereby expose it to your test project, add the following partial class de\ufb01nition to your app: public partial class Program {}.\u200c<\/p>\n<p>Listings 36.8 and 36.7 are conceptually quite di\ufb00erent too. Listing 36.7 tests that the StatusMiddleware behaves as expected in the context of a dummy ASP.NET Core app; listing 36.7 tests that your app behaves as expected for a given input. It doesn\u2019t say anything speci\ufb01c about how that happens. Your app doesn\u2019t have to use the StatusMiddleware for the test in listing 36.7 to pass; it simply has to respond correctly to the given request. That means the test knows less about the internal implementation details of your app and is concerned only with its behavior.<\/p>\n<p><b>DEFINITION<\/b> Tests that fail whenever you change your app slightly are called brittle or fragile. Try to avoid brittle tests by ensuring that they aren\u2019t dependent on the implementation details of your app.\u200c<\/p>\n<p>To create tests that use WebApplicationFactory, follow these steps:<\/p>\n<ol>\n<li>\n<p>Install the Microsoft.AspNetCore.Mvc.Testing NuGet package in your project by running dotnet add package Microsoft.AspNetCore.Mvc.Testing, by using the NuGet explorer in Visual Studio, or by adding a <code>&lt;PackageReference&gt;<\/code> element to your project \ufb01le as follows:<\/p>\n<pre><code>&lt;PackageReference Include=&quot;Microsoft.AspNetCore.Mvc.Testing&quot; Version=&quot;7.0.0&quot; \/&gt;<\/code><\/pre>\n<\/li>\n<li>\n<p>Update the <code>&lt;Project&gt;<\/code> element in your test project\u2019s .csproj \ufb01le to the following:<\/p>\n<\/li>\n<\/ol>\n<pre><code>&lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&gt;<\/code><\/pre>\n<p>This is required by WebApplicationFactory so that it can \ufb01nd your con\ufb01guration \ufb01les and static \ufb01les.<\/p>\n<ol start=\"3\">\n<li>Implement <code>IClassFixture&lt;WebApplicationFactory&lt;T&gt;&gt;<\/code> in your xUnit test class, where T is a class in your real application\u2019s project. By convention, you typically use your application\u2019s Program class for T.<\/li>\n<\/ol>\n<p>\u2022  WebApplicationFactory uses the T reference to \ufb01nd the entry point for your application, running the application in memory, and dynamically replacing Kestrel with a TestServer for tests.<\/p>\n<p>\u2022  If you\u2019re using C# top-level statements and using the Program class for T, you need to make sure that the Program class is accessible from the test project. You can change the visibility of the automatically generated Program class by adding public partial class Program {} to your app.<\/p>\n<p>\u2022  The <code>IClassFixture&lt;TFixture&gt;<\/code> is an xUnit marker interface that tells xUnit to build an instance of TFixture before building the test class and to inject the instance into the test class\u2019s constructor. You can read more about \ufb01xtures at <a href=\"https:\/\/xunit.net\/docs\/shared\">https:\/\/xunit.net\/docs\/shared<\/a>- context.<\/p>\n<ol start=\"4\">\n<li>Inject an instance of WebApplicationFactory<T> in your test class\u2019s constructor. You can use this \ufb01xture to create an HttpClient for sending in-memory requests to the TestServer. Those requests emulate your application\u2019s production behavior, as your application\u2019s real con\ufb01guration, services, and middleware are all used.<\/li>\n<\/ol>\n<p>The big advantage of WebApplicationFactory is that you can easily test your real app\u2019s behavior. That power comes with responsibility: your app will behave as it would in real life, so it will write to a database and send to third-party APIs! Depending on what you\u2019re testing, you may want to replace some of your dependencies to avoid this, as well as to make testing easier.<\/p>\n<h3>36.3.3 Replacing dependencies in WebApplicationFactory\u200c<\/h3>\n<p>When you use WebApplicationFactory to run integration tests on your app, your app will be running in-memory, but other than that, it\u2019s as though you\u2019re running your application using dotnet run. That means any connection strings, secrets, or API keys that can be loaded locally will also be used to run your application.<\/p>\n<p><b>TIP<\/b>  By default, WebApplicationFactory uses the &quot;Development&quot; hosting environment, the same as when you run locally.<\/p>\n<p>On the plus side, that means you have a genuine test that your application can start correctly. For example, if you\u2019ve forgotten to register a required DI dependency that is detected on application startup, any tests that use WebApplicationFactory will fail.<\/p>\n<p>On the downside, that means all your tests will be using the same database connection and services as when you run your application locally. It\u2019s common to want to replace those with alternative test versions of your services.<\/p>\n<p>As a simple example, imagine the CurrencyConverter that you\u2019ve been testing in this app uses IHttpClientFactory to call a third-party API to retrieve the latest exchange rates. You don\u2019t want to hit that API repeatedly in your integration tests, so you want to replace the CurrencyConverter with your own StubCurrencyConverter.<\/p>\n<p>The \ufb01rst step is to ensure that the service CurrencyConverter implements an interface\u2014 ICurrencyConverter for example\u2014and that your app uses this interface throughout, not the implementation. For our simple example, the interface would probably look like the following:<\/p>\n<pre><code>public interface ICurrencyConverter\n{\ndecimal ConvertToGbp(decimal value, decimal rate, int dps);\n}<\/code><\/pre>\n<p>You would register your real CurrencyConverter service in Program.cs using<\/p>\n<pre><code>\nbuilder.Services.AddScoped&lt;ICurrencyConverter, CurrencyConverter&gt;();<\/code><\/pre>\n<p>Now that your application depends on CurrencyConverter only indirectly, you can provide an alternative implementation in your tests.<\/p>\n<p><b>TIP<\/b>  Using an interface decouples your application services from a speci\ufb01c implementation, allowing you to substitute alternative implementations. This is a key practice for making classes testable.<\/p>\n<p>We\u2019ll create a simple alternative implementation of ICurrencyConverter for our tests that always returns the same value, 3. It\u2019s obviously not terribly useful as an actual converter, but that\u2019s not the point: you have complete control! Create the following class in your test project:<\/p>\n<pre><code>public class StubCurrencyConverter : ICurrencyConverter\n{\npublic decimal ConvertToGbp(decimal value, decimal rate, int dps)\n{\nreturn 3;\n}\n}<\/code><\/pre>\n<p>You now have all the pieces you need to replace the implementation in your tests. To achieve that, we\u2019ll use a feature of WebApplicationFactory that lets you customize the DI container before starting the test server.<\/p>\n<p><b>TIP<\/b>  It\u2019s important to remember that you want to replace the implementation only when running in the test project. I\u2019ve seen some people try to con\ufb01gure their real apps to replace live services for fake services when a speci\ufb01c value is set, for example. That is often unnecessary, bloats your apps with test services, and generally adds confusion!<\/p>\n<p>WebApplicationFactory exposes a method, WithWebHostBuilder, that allows you to customize your application before the in-memory TestServer starts. The following listing shows an integration test that uses this builder to replace the default ICurrencyConverter implementation with our test stub.\u200c<\/p>\n<p>Listing 36.9 Replacing a dependency in a test using WithWebHostBuilder<\/p>\n<pre><code>public class IntegrationTests: \u2776\nIClassFixture&lt;WebApplicationFactory&lt;Startup&gt;&gt; \u2776\n{ \u2776\nprivate readonly WebApplicationFactory&lt;Startup&gt; _fixture; \u2776\npublic IntegrationTests(WebApplicationFactory&lt;Startup&gt; fixture) \u2776\n{ \u2776\n_fixture = fixture; \u2776\n} \u2776\n[Fact]\npublic async Task ConvertReturnsExpectedValue()\n{\nvar customFactory = _fixture.WithWebHostBuilder( \u2777\n(IWebHostBuilder hostBuilder) =&gt; \u2777\n{\nhostBuilder.ConfigureTestServices(services =&gt; \u2778\n{\nservices.RemoveAll&lt;ICurrencyConverter&gt;(); \u2779\nservices.AddScoped\n&lt;ICurrencyConverter, StubCurrencyConverter&gt;(); \u277a\n});\n});\nHttpClient client = customFactory.CreateClient(); \u277b\nvar response = await client.GetAsync(&quot;\/api\/currency&quot;); \u277c\nresponse.EnsureSuccessStatusCode(); \u277c\nvar content = await response.Content.ReadAsStringAsync(); \u277c\nAssert.Equal(&quot;3&quot;, content); \u277d\n}\n}<\/code><\/pre>\n<p>\u2776 Implements the required interface and injects it into the constructor<br \/>\n\u2777 Creates a custom factory with the additional configuration<br \/>\n\u2778 ConfigureTestServices executes after all other DI services are configured in<br \/>\nyour real app.<br \/>\n\u2779 Removes all implementations of ICurrency-Converter from the DI container<br \/>\n\u277a Adds the test service as a replacement<br \/>\n\u277b Calling CreateClient bootstraps the application and starts the TestServer.<br \/>\n\u277c Invokes the currency converter endpoint<br \/>\n\u277d As the test converter always returns 3, so does the API endpoint.<\/p>\n<p>There are a couple of important points to note in this example:<\/p>\n<p>\u2022  WithWebHostBuilder() returns a new WebApplicationFactory instance. The new instance has your custom con\ufb01guration, and the original injected _fixture instance remains unchanged.<\/p>\n<p>\u2022  ConfigureTestServices() is called after your real app\u2019s ConfigureServices() method. That means you can replace services that have been previously registered. You can also use this to override con\ufb01guration values, as you\u2019ll see in section 36.4.<\/p>\n<p>WithWebHostBuilder() is handy when you want to replace a service for a single test. But what if you want to replace the ICurrencyConverter in every test? All that boiler- plate would quickly become cumbersome. Instead, you can create a custom WebApplicationFactory.<\/p>\n<h3>36.3.4 Reducing duplication by creating a custom WebApplicationFactory\u200c<\/h3>\n<p>If you \ufb01nd yourself writing WithWebHostBuilder() a lot in your integration tests, it might be worth creating a custom WebApplicationFactory instead. The follow- ing listing shows how to centralize the test service we used in listing 36.9 into a custom WebApplicationFactory.<\/p>\n<p>Listing 36.10 Creating a custom WebApplicationFactory to reduce duplication<\/p>\n<pre><code>public class CustomWebApplicationFactory \u2776\n: WebApplicationFactory&lt;Program&gt; \u2776\n{\nprotected override void ConfigureWebHost( \u2777\nIWebHostBuilder builder) \u2777\n{\nbuilder.ConfigureTestServices(services =&gt; \u2778\n{ \u2778\nservices.RemoveAll&lt;ICurrencyConverter&gt;(); \u2778\nservices.AddScoped \u2778\n&lt;ICurrencyConverter, StubCurrencyConverter&gt;(); \u2778\n}); \u2778\n}\n}<\/code><\/pre>\n<p>In this example, we override ConfigureWebHost and con\ufb01gure the test services for the factory.1 You can use your custom factory in any test by injecting it as an IClassFixture, as you have before. The following listing shows how you would update listing 36.9 to use the custom factory de\ufb01ned in listing 36.10.<\/p>\n<p>Listing 36.11 Using a custom WebApplicationFactory in an integration test<\/p>\n<pre><code>public class IntegrationTests: \u2776\nIClassFixture&lt;CustomWebApplicationFactory&gt; \u2776\n{\nprivate readonly CustomWebApplicationFactory _fixture; \u2777\npublic IntegrationTests(CustomWebApplicationFactory fixture) \u2777\n{\n_fixture = fixture;\n}\n[Fact]\npublic async Task ConvertReturnsExpectedValue()\n{\nHttpClient client = _fixture.CreateClient(); \u2778\nvar response = await client.GetAsync(&quot;\/api\/currency&quot;);\nresponse.EnsureSuccessStatusCode();\nvar content = await response.Content.ReadAsStringAsync();\nAssert.Equal(&quot;3&quot;, content); \u2779\n}\n}<\/code><\/pre>\n<p>\u2776 Implements the IClassFixture interface for the custom factory<br \/>\n\u2777 Injects an instance of the factory in the constructor<br \/>\n\u2778 The client already contains the test service configuration.<br \/>\n\u2779 The result confirms that the test service was used.<\/p>\n<p>You can also combine your custom WebApplicationFactory, which substitutes services that you always want to replace, with the WithWebHostBuilder() method to override additional services on a per-test basis. That combination gives you the best of both worlds: reduced duplication with the custom factory and control with the per-test con\ufb01guration.<\/p>\n<p>Running integration tests using your real app\u2019s con\ufb01guration provides about the closest thing you\u2019ll get to a guarantee that your app is working correctly. The sticking point in that guarantee is nearly always external dependencies, such as third-party APIs and databases.<\/p>\n<p>In the \ufb01nal section of this chapter we\u2019ll look at how to use the SQLite provider for EF Core with an in-memory database. You can use this approach to write tests for services that use an EF Core database context without needing access to a real database.\u200c<\/p>\n<h2>36.4 Isolating the database with an in-memory EF Core provider\u200c<\/h2>\n<p>In this section you\u2019ll learn how to write unit tests for code that relies on an EF Core DbContext. You\u2019ll learn how to create an in-memory database, and you\u2019ll see the di\ufb00erence between the EF in-memory provider and the SQLite in- memory provider. Finally, you\u2019ll see how to use the in- memory SQLite provider to create fast, isolated tests for code that relies on a DbContext.<\/p>\n<p>As you saw in chapter 12, EF Core is an object-relational mapper (ORM) that is used primarily with relational databases. In this section I\u2019m going to discuss one way to test services that depend on an EF Core DbContext without having to con\ufb01gure or interact with a real database.<\/p>\n<p><b>NOTE<\/b>  To learn more about testing your EF Core code, see Entity Framework Core in Action, 2nd ed., by Jon P. Smith (Manning, 2021), <a href=\"http:\/\/mng.bz\/QPpR\">http:\/\/mng.bz\/QPpR<\/a>.\u200c<\/p>\n<p>The following listing shows a highly stripped-down version of the RecipeService you created in chapter 12 for the recipe app. It shows a single method to fetch the details of a recipe using an injected EF Core DbContext.<\/p>\n<p>Listing 36.12 RecipeService to test, which uses EF Core to store and load entities<\/p>\n<pre><code>public class RecipeService\n{\nreadonly AppDbContext _context; \u2776\npublic RecipeService(AppDbContext context) \u2776\n{ \u2776\n_context = context; \u2776\n} \u2776\npublic RecipeViewModel GetRecipe(int id)\n{\nreturn _context.Recipes \u2777\n.Where(x =&gt; x.RecipeId == id)\n.Select(x =&gt; new RecipeViewModel\n{\nId = x.RecipeId,\nName = x.Name\n})\n.SingleOrDefault();\n}\n}<\/code><\/pre>\n<p>\u2776 An EF Core DbContext is injected in the constructor.<br \/>\n\u2777 Uses the <code>DbSet&lt;Recipes&gt;<\/code> property to load recipes and creates a<br \/>\nRecipeViewModel<\/p>\n<p>Writing unit tests for this class is a bit of a problem. Unit tests should be fast, repeatable, and isolated from other dependencies, but you have a dependency on your app\u2019s DbContext. You probably don\u2019t want to be writing to a real database in unit tests, as it would make the tests slow, potentially unrepeatable, and highly dependent on the con\ufb01guration of the database\u2014a failure on all three requirements!<\/p>\n<p><b>NOTE<\/b>  Depending on your development environment, you may want to use a real database for your integration tests, despite these drawbacks. Using a database like the one you\u2019ll use in production increases the likelihood that you\u2019ll detect any problems in your tests. You can \ufb01nd an example of using Docker to achieve this in Microsoft\u2019s \u201cTesting ASP.NET Core services and web apps\u201d documentation at <a href=\"http:\/\/mng.bz\/zxDw\">http:\/\/mng.bz\/zxDw<\/a>.<\/p>\n<p>Luckily, Microsoft ships two in-memory database providers for this scenario. Recall from chapter 12 that when you con\ufb01gure your app\u2019s DbContext in Program.cs, you con\ufb01gure a speci\ufb01c database provider, such as SQL Server:<\/p>\n<pre><code>builder.Services.AddDbContext&lt;AppDbContext&gt;(options =&gt; options.UseSqlServer(connectionString);<\/code><\/pre>\n<p>The in-memory database providers are alternative providers designed only for testing. Microsoft includes two in-memory providers in ASP.NET Core:<\/p>\n<p>\u2022  Microsoft.EntityFrameworkCore.InMemory\u2014This provider doesn\u2019t simulate a database. Instead, it stores objects directly in memory. It isn\u2019t a relational database as such, so it doesn\u2019t have all the features of a normal database. You can\u2019t execute SQL against it directly, and it won\u2019t enforce constraints, but it\u2019s fast. These limitations are large enough that Microsoft generally advise against using it. See <a href=\"http:\/\/mng.bz\/e1E9\">http:\/\/mng.bz\/e1E9<\/a>.<\/p>\n<p>\u2022  Microsoft.EntityFrameworkCore.Sqlite\u2014SQLite is a relational database. It\u2019s limited in features compared with a database like SQL Server, but it\u2019s a true relational database, unlike the in-memory database provider. Normally a SQLite database is written to a \ufb01le, but the provider includes an in- memory mode, in which the database stays in memory. This makes it much faster and easier to create and use for testing.<\/p>\n<p>Unfortunately, EF Core migrations are tailored to a speci\ufb01c database, which means you can\u2019t run migrations created for SQL Server or PostreSQL against a SQLite database. It\u2019s possible to create multiple sets of migrations, as described in the documentation (<a href=\"http:\/\/mng.bz\/pP15\">http:\/\/mng.bz\/pP15<\/a>), but this can add a lot of complexity. Consequently, always use EnsureCreated() with SQLite tests, which creates the database without running migrations, as you\u2019ll see in listing 36.13.<\/p>\n<p>Instead of storing data in a database on disk, both of these providers store data in memory, as shown in \ufb01gure 36.2. This makes them fast and easy to create and tear down, which allows you to create a new database for every test to ensure that your tests stay isolated from one another.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3601-1.jpg\" alt=\"alt text\" \/><br \/>\n<img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3601-2.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 36.2 The in-memory database provider and SQLite provider (in-memory mode) compared with the SQL Server database provider. The in-memory database provider doesn\u2019t simulate a database as such. Instead, it stores objects in memory and executes LINQ queries against them directly.<\/p>\n<p><b>NOTE<\/b>  In this section I describe how to use the SQLite provider as an in-memory database, as it\u2019s more full-featured than the in-memory provider. For details on using the in- memory provider, see Microsoft\u2019s \u201cEF Core In-Memory Database Provider\u201d documentation: <a href=\"http:\/\/mng.bz\/hdIq\">http:\/\/mng.bz\/hdIq<\/a>.<\/p>\n<p>To use the SQLite provider in memory, add the Microsoft.EntityFrameworkCore.Sqlite package to your test project\u2019s .csproj \ufb01le. This adds the UseSqlite() extension method, which you\u2019ll use to con\ufb01gure the database provider for your unit tests.<\/p>\n<p>Listing 36.13 shows how you could use the in-memory SQLite provider to test the GetRecipe() method of RecipeService. Start by creating a SqliteConnection object and using the &quot;DataSource=:memory:&quot; connection string. This tells the provider to store the database in memory and then open the connection. This is typically faster than using a \ufb01le-based connection-string and means you can easily run multiple tests in parallel, as there\u2019s no shared database.\u200c<\/p>\n<p><b>WARNING<\/b> The SQlite in-memory database is destroyed when the connection is closed. If you don\u2019t open the connection yourself, EF Core closes the connection to the in- memory database when you dispose of the DbContext. If you want to share an in-memory database between DbContexts, you must explicitly open the connection yourself.<\/p>\n<p>Next, pass the SqliteConnection instance into the DbContextOptionsBuilder&lt;&gt; and call UseSqlite(). This con\ufb01gures the resulting DbContextOptions&lt;&gt; object with the necessary services for the SQLite provider and provides the connection to the in-memory database.\u200cBecause you\u2019re passing this options object in to an instance of AppDbContext, all calls to the DbContext result in calls to the in-memory database provider.<\/p>\n<p>Listing 36.13 Using the in-memory database provider to test an EF Core DbContext<\/p>\n<pre><code>[Fact]\npublic void GetRecipeDetails_CanLoadFromContext()\n{\nvar connection = new SqliteConnection(&quot;DataSource=:memory:&quot;); \u2776\nconnection.Open(); \u2777\nvar options = new DbContextOptionsBuilder&lt;AppDbContext&gt;() \u2778\n.UseSqlite(connection) \u2778\n.Options; \u2778\nusing (var context = new AppDbContext(options)) \u2779\n{\ncontext.Database.EnsureCreated(); \u277a\ncontext.Recipes.AddRange( \u277b\nnew Recipe { RecipeId = 1, Name = &quot;Recipe1&quot; }, \u277b\nnew Recipe { RecipeId = 2, Name = &quot;Recipe2&quot; }, \u277b\nnew Recipe { RecipeId = 3, Name = &quot;Recipe3&quot; }); \u277b\ncontext.SaveChanges(); \u277c\n}\nusing (var context = new AppDbContext(options)) \u277d\n{\nvar service = new RecipeService(context); \u277e\nvar recipe = service.GetRecipe (id: 2); \u277f\nAssert.NotNull(recipe); \u24eb\nAssert.Equal(2, recipe.Id); \u24eb\nAssert.Equal(&quot;Recipe2&quot;, recipe.Name); \u24eb\n}\n}<\/code><\/pre>\n<p>\u2776 Configures an in-memory SQLite connection using the special \u201cin-memory\u201d connection string<br \/>\n\u2777 Opens the connection so EF Core won\u2019t close it automatically<br \/>\n\u2778 Creates an instance of DbContextOptions&lt;&gt; and configures it to use the SQLite connection<br \/>\n\u2779 Creates a DbContext and passes in the options<br \/>\n\u277a Ensures that the in-memory database matches EF Core\u2019s model (similar to running migrations)<br \/>\n\u277b Adds some recipes to the DbContext<br \/>\n\u277c Saves the changes to the in-memory database<br \/>\n\u277d Creates a fresh DbContext to test that you can retrieve data from the DbContext<br \/>\n\u277e Creates the Recipe-Service to test and pass in the fresh DbContext<br \/>\n\u277f Executes the GetRecipe function. This executes the query against the inmemory database.<br \/>\n\u24eb Verifies that you retrieved the recipe correctly from the in-memory database<\/p>\n<p>This example follows the standard format for any time you need to test a class that depends on an EF Core DbContext:<\/p>\n<ol>\n<li>\n<p>Create a SqliteConnection with the &quot;DataSource=:memory:&quot; connection string, and open the connection.<\/p>\n<\/li>\n<li>\n<p>Create a <code>DbContextOptionsBuilder&lt;&gt;<\/code> and call UseSqlite(), passing in the open connection.<\/p>\n<\/li>\n<li>\n<p>Retrieve the DbContextOptions object from the Options property.<\/p>\n<\/li>\n<li>\n<p>Pass the options to an instance of your DbContext and ensure the database matches EF Core\u2019s model by calling context.Database.EnsureCreated(). This is similar to running migrations on your database, but it should be used only on test databases. Create and add any required test data to the in- memory database, and call SaveChanges() to persist the data.<\/p>\n<\/li>\n<li>\n<p>Create a new instance of your DbContext and inject it into your test class. All queries will be executed against the in-memory database.<\/p>\n<\/li>\n<\/ol>\n<p>By using a separate DbContext for each purpose, you can avoid bugs in your tests due to EF Core caching data without writing it to the database. With this approach, you can be sure that any data read in the second DbContext was persisted to the underlying in-memory database provider.<\/p>\n<p>This was a brief introduction to using the SQLite provider as an in-memory database provider and EF Core testing in general, but if you follow the setup shown in listing 36.13, it should take you a long way. The source code for this chapter shows how you can combine this code with a custom WebApplicationFactory to use an in-memory database for your integration tests. For more details on testing EF Core, including additional options and strategies, see Entity Framework Core in Action, 2nd ed., by Jon P. Smith (Manning, 2021).\u200c\u200c<\/p>\n<h2>Summary<\/h2>\n<p>Use the DefaultHttpContext class to unit-test your custom middleware components. If you need access to the response body, you must replace the default Stream.Null with a MemoryStream instance and read the stream manually after invoking the middleware.<\/p>\n<p>API controllers, minimal APIs, and Razor Page models can be unit-tested like other classes, but they should generally contain little business logic, so it may not be worth the e\ufb00ort. For example, the API controller is tested independently of routing, model validation, and \ufb01lters, so you can\u2019t easily test logic that depends on any of these aspects.<\/p>\n<p>Integration tests allow you to test multiple components of your app at the same time, typically within the context of the ASP.NET Core framework itself. The Microsoft.AspNetCore.TestHost package provides a TestServer object that you can use to create a simple web host for testing. This creates an in- memory server that you can make requests to and receive responses from. You can use the TestServer directly when you wish to create integration tests for custom components like middleware.<\/p>\n<p>For more extensive integration tests of a real application, you should use the WebApplicationFactory class in the Microsoft.AspNetCore.Mvc.Testing package.<\/p>\n<p>Implement <code>IClassFixture&lt;WebApplicationFactory&lt;P rogram&gt;&gt;<\/code> on your test class, and inject an instance of <code>WebApplicationFactory&lt;Program&gt;<\/code> into the constructor. This creates an in-memory version of your whole app, using the same con\ufb01guration, DI services, and middleware pipeline. You can send in- memory requests to your app to get the best idea of how your application will behave in production.<\/p>\n<p>To customize the WebApplicationFactory, call WithWebHostBuilder() and then call ConfigureTestServices(). This method is invoked after your app\u2019s standard DI con\ufb01guration. This enables you to add or remove the default services for your app, such as to replace a class that contacts a third-party API with a stub implementation.<\/p>\n<p>If you need to customize the services for every test, you can create a custom WebApplicationFactory by deriving from it and overriding the ConfigureWebHost method. You can place all your con\ufb01guration in the custom factory and implement <code>IClassFixture&lt;CustomWebApplicationFac tory&gt;<\/code> in your test classes instead of calling WithWebHostBuilder() in every test method.<\/p>\n<p>You can use the EF Core SQLite provider as an in- memory database to test code that depends on an EF Core database context. You con\ufb01gure the in- memory provider by creating a SqliteConnection with a &quot;DataSource=:memory:&quot; connection string.<\/p>\n<p>Create a <code>DbContextOptionsBuilder&lt;&gt;<\/code> object and call UseSqlite(), passing in the connection. Finally, pass <code>DbContextOptions&lt;&gt;<\/code> into an instance of your app\u2019s DbContext, and call context.Database.EnsureCreated() to prepare the in-memory database for use with EF Core.<\/p>\n<p>The SQLite in-memory database is maintained as long as there\u2019s an open SqliteConnection.<\/p>\n<p>When you open the connection manually, the database can be used with multiple DbContexts. If you don\u2019t call Open() on the connection, EF Core will close the connection (and delete the in- memory database) when the DbContext is disposed of.<\/p>\n<ol>\n<li>WebApplicationFactory has many other methods you could override for other scenarios. For details, see <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/test\/integration-tests\">https:\/\/learn.microsoft.com\/aspnet\/core\/test\/integration-tests<\/a>.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>36 Testing ASP.NET Core applications\u200c This chapter covers \u2022 Writing unit tests for custom middleware, API controllers, and minimal API endpoints \u2022 Using the Test Host package to write integration tests Testing your real application\u2019s behavior with WebApplicationFactory \u2022 Testing code dependent on Entity Framework Core with the in-memory database provider In chapter 35 I [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-643","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/643","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=643"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/643\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=643"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=643"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=643"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}