ASP.NET CORE 2.* 利用集成测试框架覆盖HttpClient相关代码

  • 2019 年 10 月 3 日
  • 筆記

ASP.NET CORE 集成测试官方介绍

我的asp.net core 项目里面大部分功能都是去调用别人的API ,大量使用HttpClient,公司单元测试覆盖率要求95%以上,很难做到不mock HttpClient 达到这个指数。

以下方法是我自己总结的在单元测试里 mock httpClient 的方式,基本思路是利用集成测试框架,mock外部调用的API ,达到httpClient 代码的覆盖。

代码地址:https://github.com/Halo-Shaka/LearningAspNetCoreIntegrationTesting.git

 

举个例子,创建一个简单的asp.net core 项目,里面只有一个api , api/values, 是个get 方法,

get 方法内部是去调用外部API, 随便写个方法  向google 发一个信息。

   [Route("api/[controller]")]      [ApiController]      public class ValuesController : ControllerBase      {          private readonly IHttpClientFactory _httpClientFactory;            private readonly IOptions<AppSettings> _options;            public ValuesController(IHttpClientFactory httpClientFactory, IOptions<AppSettings> options)          {              _httpClientFactory = httpClientFactory;              _options = options;          }            // GET api/values          [HttpGet]          public async Task<ActionResult> Get()          {              var client = _httpClientFactory.CreateClient();                var url = _options.Value.Url;              var payload = new              {                  From = "China"              };                var requestMessage = new HttpRequestMessage(HttpMethod.Post, url)              {                  Content = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json")              };                try              {                  var response = await client.SendAsync(requestMessage);                  var content = await response.Content.ReadAsStringAsync();                    if (response.StatusCode == HttpStatusCode.OK)                  {                      return Ok(content);                  }                    return BadRequest();              }              catch (Exception e)              {                  return StatusCode(502);              }          }      }  

  

 

这里面有个需要注意的地方,使用注入的httpClient, 外部访问的地址需要是配置的

 public class Startup      {          public Startup(IConfiguration configuration)          {              Configuration = configuration;          }            public IConfiguration Configuration { get; }            // This method gets called by the runtime. Use this method to add services to the container.          public void ConfigureServices(IServiceCollection services)          {              services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);              services.AddHttpClient();          }            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.          public void Configure(IApplicationBuilder app, IHostingEnvironment env)          {              if (env.IsDevelopment())              {                  app.UseDeveloperExceptionPage();              }              else              {                  // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.                  app.UseHsts();              }                app.UseHttpsRedirection();              app.UseMvc();          }      }  

  

到此为止,基本功能就写完了,现在来写测试代码 

添加 XUnit单元测试项目,添加如下包

Microsoft.AspNetCore.App

Microsoft.AspNetCore.Mvc.Testing

Microsoft.NET.Test.Sdk

Moq

利用集成测试的虚拟站点,把我们需要调用的外部API 伪造出来,

 [Route("gateway")]      public class MockGatewayController : ControllerBase      {          [HttpPost]          public ActionResult<string> Logon([FromBody]LogonRequest request)          {              if (request.From == "China")              {                  var behavior = MockGatewayData.MockBehavior;                  return behavior.LogonResult();              }                return string.Empty;          }      }        public class LogonRequest      {          public string From { get; set; }      }        public interface IGatewayMockBehavior      {          ActionResult<string> LogonResult();      }        public class MockGatewayData      {          public static IGatewayMockBehavior MockBehavior { get; set; }      }
MockGatewayData类的作用是 让客户端能够访问到服务端,并指定想要返回的结果
接着创建 GenericWebApplicationFactory,并把刚伪造的 controller 指定到虚拟站点里面,

    public class GenericWebApplicationFactory : WebApplicationFactory<Startup>      {          protected override void ConfigureWebHost(IWebHostBuilder builder)          {              builder.ConfigureServices(services =>              {                  services.AddMvc().AddApplicationPart(typeof(MockGatewayController).Assembly).AddControllersAsServices();              });          }      }

最后写测试代码

 public class ValuesControllerTest : IClassFixture<GenericWebApplicationFactory>      {          public ValuesControllerTest(GenericWebApplicationFactory factory, ITestOutputHelper output)          {              this.factory = factory;              this.output = output;          }            protected GenericWebApplicationFactory factory;          protected ITestOutputHelper output;              [Fact]          public void GetRequest_GatewayInaccessible_ShouldReturn502()          {              var client = factory.WithWebHostBuilder(p => p.ConfigureServices(services =>              {                  services.PostConfigure<AppSettings>(options => { options.Url = "https://aaaaaaaa"; });              })).CreateClient();              var response = client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/values")).Result;              Assert.Equal(HttpStatusCode.BadGateway, response.StatusCode);          }            [Fact]          public void GetRequest_GatewayOnFailed_ShouldReturn400()          {              var behavior = new Mock<IGatewayMockBehavior>();              behavior.Setup(p => p.LogonResult()).Returns(new BadRequestResult());              MockGatewayData.MockBehavior = behavior.Object;                var client = CreateHttpClient();              var response = client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/values")).Result;              Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);          }            [Fact]          public void GetRequest_GatewayOnSuccess_ShouldReturn200()          {              var behavior = new Mock<IGatewayMockBehavior>();              behavior.Setup(p => p.LogonResult()).Returns(new ActionResult<string>("success"));              MockGatewayData.MockBehavior = behavior.Object;                var client = CreateHttpClient();              var response = client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/values")).Result;              Assert.Equal(HttpStatusCode.OK, response.StatusCode);          }            private HttpClient CreateHttpClient()          {              var client = factory.WithWebHostBuilder(p => p.ConfigureServices(services =>              {                  services.PostConfigure<AppSettings>(options => { options.Url = "http://localhost/gateway"; });                    services.AddSingleton(typeof(IHttpClientFactory), new MockHttpClientFactory                  {                      InjectHttpClient = factory.CreateClient                  });              })).CreateClient();                return client;          }      }

最后看下覆盖率,整个controller 里面httpClient  全都被覆盖了

 

代码地址:

https://github.com/Halo-Shaka/LearningAspNetCoreIntegrationTesting.git

 

Exit mobile version