为AspNetCore中间件编写单元测试

1.编辑单元测试项目文件
   例如使用xUnitTest项目时,在项目文件中的Project节点下加入如下代码:

1     <ItemGroup>
2         <FrameworkReference Include="Microsoft.AspNetCore.App"></FrameworkReference>
3     </ItemGroup>

2.配置和构建Host

   例如配置服务、指定要使用的服务器以及加入需要测试的中间件,主要代码如下:

 1     using IHost host = new HostBuilder()
 2     .ConfigureWebHost(webHostBuilder =>
 3         {
 4             webHostBuilder
 5             .ConfigureServices(services =>
 6             {
 7                 services.AddHttpContextAccessor();
 8                 services.AddLogging();
 9                 services.AddRouting();
10             })
11             .UseKestrel()
12             .UseUrls(GetTestUrl(server))
13             .UseWebRoot(AppContext.BaseDirectory)
14             .Configure(app =>
15             {
16                 app.UseRouting();
17 
18                 app.Use(next => context =>
19                 {
20                     // Assign an endpoint, this will make the default files noop.
21                     context.SetEndpoint(new Endpoint((c) =>
22                         {
23                             return context.Response.WriteAsync("endpoint.");
24                         },
25                         new EndpointMetadataCollection(), "test"));
26 
27                     return next(context);
28                 });
29 
30                 app.UseStaticFiles();
31 
32                 //这里是需要单测的中间件
33                 app.UseDemoMiddleware();
34 
35                 app.UseEndpoints(endpoints => { });
36             });
37         })
38     .Build();

3.启动和使用Host
   配置完Host后,将该Host实例启动,并且使用HttpClient模拟请求,以期执行自定义的中间件,主要代码如下:

1     await host.StartAsync();
2 
3     using var client = new HttpClient { BaseAddress = new Uri(GetAddress(host)) };
4     string url = "/api/demo";
5     var response = await client.GetAsync(url);
6     
7     Assert.Equal(HttpStatusCode.OK, response.StatusCode);

4.完整示例如下
   
4.1 ServerType

1     public enum ServerType
2     {
3         None,
4         IISExpress,
5         IIS,
6         HttpSys,
7         Kestrel,
8         Nginx
9     }

   4.2 TestUriHelper

 1     public static class TestUriHelper
 2     {
 3         public static Uri BuildTestUri(ServerType serverType)
 4         {
 5             return BuildTestUri(serverType, hint: null);
 6         }
 7 
 8         public static Uri BuildTestUri(ServerType serverType, string hint)
 9         {
10             // Assume status messages are enabled for Kestrel and disabled for all other servers.
11             var statusMessagesEnabled = (serverType == ServerType.Kestrel);
12 
13             return BuildTestUri(serverType, Uri.UriSchemeHttp, hint, statusMessagesEnabled);
14         }
15 
16         internal static Uri BuildTestUri(ServerType serverType, string scheme, string hint, bool statusMessagesEnabled)
17         {
18             if (string.IsNullOrEmpty(hint))
19             {
20                 if (serverType == ServerType.Kestrel && statusMessagesEnabled)
21                 {
22                     // Most functional tests use this codepath and should directly bind to dynamic port "0" and scrape
23                     // the assigned port from the status message, which should be 100% reliable since the port is bound
24                     // once and never released.  Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not
25                     // supported, so the port is only bound on "127.0.0.1" (IPv4).  If a test explicitly requires IPv6,
26                     // it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only).
27                     return new UriBuilder(scheme, "127.0.0.1", 0).Uri;
28                 }
29                 else if (serverType == ServerType.HttpSys)
30                 {
31                     Debug.Assert(scheme == "http", "Https not supported");
32                     return new UriBuilder(scheme, "localhost", 0).Uri;
33                 }
34                 else
35                 {
36                     // If the server type is not Kestrel, or status messages are disabled, there is no status message
37                     // from which to scrape the assigned port, so the less reliable GetNextPort() must be used.  The
38                     // port is bound on "localhost" (both IPv4 and IPv6), since this is supported when using a specific
39                     // (non-zero) port.
40                     return new UriBuilder(scheme, "localhost", GetNextPort()).Uri;
41                 }
42             }
43             else
44             {
45                 var uriHint = new Uri(hint);
46                 if (uriHint.Port == 0)
47                 {
48                     // Only a few tests use this codepath, so it's fine to use the less reliable GetNextPort() for simplicity.
49                     // The tests using this codepath will be reviewed to see if they can be changed to directly bind to dynamic
50                     // port "0" on "127.0.0.1" and scrape the assigned port from the status message (the default codepath).
51                     return new UriBuilder(uriHint) { Port = GetNextPort() }.Uri;
52                 }
53                 else
54                 {
55                     // If the hint contains a specific port, return it unchanged.
56                     return uriHint;
57                 }
58             }
59         }
60 
61         private static int GetNextPort()
62         {
63             using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
64             socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
65             return ((IPEndPoint)socket.LocalEndPoint).Port;
66         }
67     }

   4.3 DemoMiddlewareUnitTest

 1     public class DemoMiddlewareUnitTest
 2     {
 3         [Fact]
 4         public async Task DemoMiddlewareTest()
 5         {
 6             await DemoMiddleware(ServerType.IIS);
 7             await DemoMiddleware(ServerType.IISExpress);
 8             await DemoMiddleware(ServerType.Kestrel);
 9             await DemoMiddleware(ServerType.Nginx);
10         }
11 
12         private async Task DemoMiddleware(ServerType server)
13         {
14             using IHost host = new HostBuilder()
15                 .ConfigureWebHost(webHostBuilder =>
16                     {
17                         webHostBuilder
18                         .ConfigureServices(services =>
19                         {
20                             services.AddHttpContextAccessor();
21                             services.AddLogging();
22                             services.AddRouting();
23                         })
24                         .UseKestrel()
25                         .UseUrls(GetTestUrl(server))
26                         .UseWebRoot(AppContext.BaseDirectory)
27                         .Configure(app =>
28                         {
29                             app.UseRouting();
30 
31                             app.Use(next => context =>
32                             {
33                                 // Assign an endpoint, this will make the default files noop.
34                                 context.SetEndpoint(new Endpoint((c) =>
35                                     {
36                                         return context.Response.WriteAsync("endpoint.");
37                                     },
38                                     new EndpointMetadataCollection(), "test"));
39 
40                                 return next(context);
41                             });
42 
43                             app.UseStaticFiles();
44 
45                             app.UseDemoMiddleware();
46 
47                             app.UseEndpoints(endpoints => { });
48                         });
49                     })
50                 .Build();
51 
52             await host.StartAsync();
53 
54             using var client = new HttpClient { BaseAddress = new Uri(GetAddress(host)) };
55             string url = "/api/demo";
56             var response = await client.GetAsync(url);
57 
58             Assert.Equal(HttpStatusCode.OK, response.StatusCode);
59             Assert.Equal("endpoint.", await response.Content.ReadAsStringAsync());
60         }
61 
62         private string GetTestUrl(ServerType serverType)
63         {
64             return TestUriHelper.BuildTestUri(serverType).ToString();
65         }
66 
67         private string GetAddress(IHost server)
68         {
69             return server.Services.GetService<IServer>().Features.Get<IServerAddressesFeature>().Addresses.First();
70         }
71     }

 

posted on 2021-01-27 19:34  庭前花满留晚照  阅读(123)  评论(0编辑  收藏  举报

导航