ASP.NET CORE 2.* 利用集成測試框架覆蓋HttpClient相關代碼


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM