Skip to content

Commit e8a7585

Browse files
authored
Test middleware topic (#18114)
1 parent e36e684 commit e8a7585

File tree

8 files changed

+205
-2
lines changed

8 files changed

+205
-2
lines changed

aspnetcore/fundamentals/middleware/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn about ASP.NET Core middleware and the request pipeline.
55
monikerRange: '>= aspnetcore-2.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 04/06/2020
8+
ms.date: 5/6/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: fundamentals/middleware/index
1111
---
@@ -256,6 +256,7 @@ ASP.NET Core ships with the following middleware components. The *Order* column
256256
## Additional resources
257257

258258
* <xref:fundamentals/middleware/write>
259+
* <xref:test/middleware>
259260
* <xref:migration/http-modules>
260261
* <xref:fundamentals/startup>
261262
* <xref:fundamentals/request-features>
@@ -459,6 +460,7 @@ ASP.NET Core ships with the following middleware components. The *Order* column
459460
## Additional resources
460461

461462
* <xref:fundamentals/middleware/write>
463+
* <xref:test/middleware>
462464
* <xref:migration/http-modules>
463465
* <xref:fundamentals/startup>
464466
* <xref:fundamentals/request-features>

aspnetcore/fundamentals/middleware/write.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn how to write custom ASP.NET Core middleware.
55
monikerRange: '>= aspnetcore-2.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 08/22/2019
8+
ms.date: 5/6/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: fundamentals/middleware/write
1111
---
@@ -80,6 +80,7 @@ The following code calls the middleware from `Startup.Configure`:
8080
## Additional resources
8181

8282
* <xref:fundamentals/middleware/index>
83+
* <xref:test/middleware>
8384
* <xref:migration/http-modules>
8485
* <xref:fundamentals/startup>
8586
* <xref:fundamentals/request-features>

aspnetcore/test/middleware.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
title: Test ASP.NET Core middleware
3+
author: tratcher
4+
description: Learn how to test ASP.NET Core middleware with TestServer.
5+
ms.author: riande
6+
ms.custom: mvc
7+
ms.date: 5/6/2019
8+
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
9+
uid: test/middleware
10+
---
11+
# Test ASP.NET Core middleware
12+
13+
By [Chris Ross](https://github.com/Tratcher)
14+
15+
Middleware can be tested in isolation with <xref:Microsoft.AspNetCore.TestHost.TestServer>. It allows you to:
16+
17+
* Instantiate an app pipeline containing only the components that you need to test.
18+
* Send custom requests to verify middleware behavior.
19+
20+
Advantages:
21+
22+
* Requests are sent in-memory rather than being serialized over the network.
23+
* This avoids additional concerns, such as port management and HTTPS certificates.
24+
* Exceptions in the middleware can flow directly back to the calling test.
25+
* It's possible to customize server data structures, such as <xref:Microsoft.AspNetCore.Http.HttpContext>, directly in the test.
26+
27+
## Set up the TestServer
28+
29+
In the test project, create a test:
30+
31+
* Build and start a host that uses <xref:Microsoft.AspNetCore.TestHost.TestServer>.
32+
* Add any required services that the middleware uses.
33+
* Configure the processing pipeline to use the middleware for the test.
34+
35+
[!code-csharp[](middleware/samples_snapshot/3.x/setup.cs?highlight=4-18)]
36+
37+
## Send requests with HttpClient
38+
Send a request using <xref:System.Net.Http.HttpClient>:
39+
40+
[!code-csharp[](middleware/samples_snapshot/3.x/request.cs?highlight=20)]
41+
42+
Assert the result. First, make an assertion the opposite of the result that you expect. An initial run with a false positive assertion confirms that the test fails when the middleware is performing correctly. Run the test and confirm that the test fails.
43+
44+
In the following example, the middleware should return a 404 status code (*Not Found*) when the root endpoint is requested. Make the first test run with `Assert.NotEqual( ... );`, which should fail:
45+
46+
[!code-csharp[](middleware/samples_snapshot/3.x/false-failure-check.cs?highlight=22)]
47+
48+
Change the assertion to test the middleware under normal operating conditions. The final test uses `Assert.Equal( ... );`. Run the test again to confirm that it passes.
49+
50+
[!code-csharp[](middleware/samples_snapshot/3.x/final-test.cs?highlight=22)]
51+
52+
## Send requests with HttpContext
53+
54+
A test app can also send a request using [SendAsync(Action\<HttpContext>, CancellationToken)](xref:Microsoft.AspNetCore.TestHost.TestServer.SendAsync%2A). In the following example, several checks are made when `https://example.com/A/Path/?and=query` is processed by the middleware:
55+
56+
```csharp
57+
[Fact]
58+
public async Task TestMiddleware_ExpectedResponse()
59+
{
60+
using var host = await new HostBuilder()
61+
.ConfigureWebHost(webBuilder =>
62+
{
63+
webBuilder
64+
.UseTestServer()
65+
.ConfigureServices(services =>
66+
{
67+
services.AddMyServices();
68+
})
69+
.Configure(app =>
70+
{
71+
app.UseMiddleware<MyMiddleware>();
72+
});
73+
})
74+
.StartAsync();
75+
76+
var server = host.GetTestServer();
77+
server.BaseAddress = new Uri("https://example.com/A/Path/");
78+
79+
var context = await server.SendAsync(c =>
80+
{
81+
c.Request.Method = HttpMethods.Post;
82+
c.Request.Path = "/and/file.txt";
83+
c.Request.QueryString = new QueryString("?and=query");
84+
});
85+
86+
Assert.True(context.RequestAborted.CanBeCanceled);
87+
Assert.Equal(HttpProtocol.Http11, context.Request.Protocol);
88+
Assert.Equal("POST", context.Request.Method);
89+
Assert.Equal("https", context.Request.Scheme);
90+
Assert.Equal("example.com", context.Request.Host.Value);
91+
Assert.Equal("/A/Path", context.Request.PathBase.Value);
92+
Assert.Equal("/and/file.txt", context.Request.Path.Value);
93+
Assert.Equal("?and=query", context.Request.QueryString.Value);
94+
Assert.NotNull(context.Request.Body);
95+
Assert.NotNull(context.Request.Headers);
96+
Assert.NotNull(context.Response.Headers);
97+
Assert.NotNull(context.Response.Body);
98+
Assert.Equal(404, context.Response.StatusCode);
99+
Assert.Null(context.Features.Get<IHttpResponseFeature>().ReasonPhrase);
100+
}
101+
```
102+
103+
<xref:Microsoft.AspNetCore.TestHost.TestServer.SendAsync%2A> permits direct configuration of an <xref:Microsoft.AspNetCore.Http.HttpContext> object rather than using the <xref:System.Net.Http.HttpClient> abstractions. Use <xref:Microsoft.AspNetCore.TestHost.TestServer.SendAsync%2A> to manipulate structures only available on the server, such as [HttpContext.Items](xref:Microsoft.AspNetCore.Http.HttpContext.Items) or [HttpContext.Features](xref:Microsoft.AspNetCore.Http.HttpContext.Features).
104+
105+
As with the earlier example that tested for a *404 - Not Found* response, check the opposite for each `Assert` statement in the preceding test. The check confirms that the test fails correctly when the middleware is operating normally. After you've confirmed that the false positive test works, set the final `Assert` statements for the expected conditions and values of the test. Run it again to confirm that the test passes.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[Fact]
2+
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
3+
{
4+
using var host = await new HostBuilder()
5+
.ConfigureWebHost(webBuilder =>
6+
{
7+
webBuilder
8+
.UseTestServer()
9+
.ConfigureServices(services =>
10+
{
11+
services.AddMyServices();
12+
})
13+
.Configure(app =>
14+
{
15+
app.UseMiddleware<MyMiddleware>();
16+
});
17+
})
18+
.StartAsync();
19+
20+
var testServer = host.GetTestServer();
21+
22+
var testClient = testServer.CreateClient();
23+
var response = await testClient.GetAsync("/");
24+
25+
Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode);
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[Fact]
2+
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
3+
{
4+
using var host = await new HostBuilder()
5+
.ConfigureWebHost(webBuilder =>
6+
{
7+
webBuilder
8+
.UseTestServer()
9+
.ConfigureServices(services =>
10+
{
11+
services.AddMyServices();
12+
})
13+
.Configure(app =>
14+
{
15+
app.UseMiddleware<MyMiddleware>();
16+
});
17+
})
18+
.StartAsync();
19+
20+
var response = await host.GetTestServer().CreateClient().GetAsync("/");
21+
22+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[Fact]
2+
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
3+
{
4+
using var host = await new HostBuilder()
5+
.ConfigureWebHost(webBuilder =>
6+
{
7+
webBuilder
8+
.UseTestServer()
9+
.ConfigureServices(services =>
10+
{
11+
services.AddMyServices();
12+
})
13+
.Configure(app =>
14+
{
15+
app.UseMiddleware<MyMiddleware>();
16+
});
17+
})
18+
.StartAsync();
19+
20+
var response = await host.GetTestServer().CreateClient().GetAsync("/");
21+
22+
...
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[Fact]
2+
public async Task MiddlewareTest_ReturnsNotFoundForRequest()
3+
{
4+
using var host = await new HostBuilder()
5+
.ConfigureWebHost(webBuilder =>
6+
{
7+
webBuilder
8+
.UseTestServer()
9+
.ConfigureServices(services =>
10+
{
11+
services.AddMyServices();
12+
})
13+
.Configure(app =>
14+
{
15+
app.UseMiddleware<MyMiddleware>();
16+
});
17+
})
18+
.StartAsync();
19+
20+
...
21+
}

aspnetcore/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,8 @@
665665
uid: test/razor-pages-tests
666666
- name: Test controllers
667667
uid: mvc/controllers/testing
668+
- name: Test middleware
669+
uid: test/middleware
668670
- name: Remote debugging
669671
href: /visualstudio/debugger/remote-debugging-azure
670672
- name: Snapshot debugging

0 commit comments

Comments
 (0)