WireMock.NET如何帮助进行.NET Core应用程序的集成测试
目录
介绍
背景
使用代码
兴趣点
- 从 GitHub下载完整的源代码
介绍
如果您是执行TDD的ASP.NET Core开发人员,您可能会遇到一些问题。您的测试不涵盖您的Program类和Startup类。您的模拟框架有助于模拟内部依赖项,但不能模拟外部依赖项,例如其他公司制作的Web服务。此外,也许您决定不测试某些类,因为要模拟的内部依赖项太多。在本文中,我将解释如何解决这些问题。
背景
如果您对.NET Core 3.1(我在这里使用的版本)的TDD有一些经验,这将很有帮助,最好使用xUnit。
使用代码
首先,让我们实现该ConfigureServices方法。我们依赖于appsettings.json文件中设置的外部服务和依赖于HttpClient.
添加了重试策略以确保在意外失败时重试请求。
public void ConfigureServices(IServiceCollection services)
{services.AddControllers();var googleLocation = Configuration["Google"];services.AddHttpClient<ISearchEngineService, SearchEngineService>(c =>c.BaseAddress = new Uri(googleLocation)).SetHandlerLifetime(TimeSpan.FromMinutes(5)).AddPolicyHandler(GetRetryPolicy());
} private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{return HttpPolicyExtensions.HandleTransientHttpError().OrTransientHttpStatusCode().WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}
此外,还需要实现这个类来实例化以进行依赖注入(到控制器中)。只有一种方法。它调用外部服务并返回字符数。
public class SearchEngineService : ISearchEngineService
{private readonly HttpClient _httpClient;public SearchEngineService(HttpClient httpClient){_httpClient = httpClient;}public async Task<int> GetNumberOfCharactersFromSearchQuery(string toSearchFor){var result = await _httpClient.GetAsync($"/search?q={toSearchFor}"); var content = await result.Content.ReadAsStringAsync();return content.Length;}
}
从逻辑上讲,我们也需要实现控制器。
[Route("api/[controller]")]
[ApiController]
public class SearchEngineController : ControllerBase
{private readonly ISearchEngineService _searchEngineService;public SearchEngineController(ISearchEngineService searchEngineService){_searchEngineService = searchEngineService;}[HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")]public async Task<ActionResult<int>> GetNumberOfCharacters(string queryEntry){var numberOfCharacters = await _searchEngineService.GetNumberOfCharactersFromSearchQuery(queryEntry);return Ok(numberOfCharacters);}
}
要使用来自自动化测试的Web请求测试所有内容,我们需要对Web应用程序进行自托管(在xUnit测试期间)。为此,我们需要在基类中使用WebApplicationFactory:
public abstract class TestBase : IDisposable, IClassFixture<WebApplicationFactory<Startup>>
{protected readonly HttpClient HttpClient;public TestBase(WebApplicationFactory<Startup> factory, int portNumber, bool useHttps){var extraConfiguration = GetConfiguration();string afterHttp = useHttps ? "s" : "";HttpClient = factory.WithWebHostBuilder(whb =>{whb.ConfigureAppConfiguration((context, configbuilder) =>{configbuilder.AddInMemoryCollection(extraConfiguration);});}).CreateClient(new WebApplicationFactoryClientOptions{BaseAddress = new Uri($"http{afterHttp}://localhost:{portNumber}")});}protected virtual Dictionary<string, string> GetConfiguration(){return new Dictionary<string, string>();}protected virtual void Dispose(bool disposing){if (disposing){HttpClient.Dispose();}}public void Dispose(){Dispose(true);GC.SuppressFinalize(this);}
}
这个基类做了以下事情:
- 创建一个HttpClient对我们自己的应用程序进行REST调用而不启动它(由CreateClient完成)
- 运行Startup和Program类中的代码(也由CreateClient完成)
- 使用AddInMemoryCollection专门为我们的测试更新配置
- 每次测试后释放HttpClient
现在我们有了基类,我们可以实现实际的测试。
public class SearchEngineClientTest : TestBase
{private FluentMockServer _mockServerSearchEngine;public SearchEngineClientTest(WebApplicationFactory<Startup> factory) : base(factory, 5347, false){}[Theory][InlineData("Daan","SomeResponseFromGoogle")][InlineData("Sean","SomeOtherResponseFromGoogle")]public async Task TestWithStableServer(string searchQuery, string externalResponseContent){SetupStableServer(externalResponseContent);var response = await HttpClient.GetAsync($"/api/searchengine/{searchQuery}");response.EnsureSuccessStatusCode();var actualResponseContent = await response.Content.ReadAsStringAsync();Assert.Equal($"{externalResponseContent.Length}", actualResponseContent);var requests = _mockServerSearchEngine.LogEntries.Select(l => l.RequestMessage).ToList();Assert.Single(requests);Assert.Contains($"/search?q={searchQuery}", requests.Single().AbsoluteUrl);}[Theory][InlineData("Daan", "SomeResponseFromGoogle")][InlineData("Sean", "SomeOtherResponseFromGoogle")]public async Task TestWithUnstableServer(string searchQuery, string externalResponseContent){SetupUnStableServer(externalResponseContent);var response = await HttpClient.GetAsync($"/api/searchengine/{searchQuery}");response.EnsureSuccessStatusCode();var actualResponseContent = await response.Content.ReadAsStringAsync();Assert.Equal($"{externalResponseContent.Length}", actualResponseContent);var requests = _mockServerSearchEngine.LogEntries.Select(l => l.RequestMessage).ToList();Assert.Equal(2,requests.Count);Assert.Contains($"/search?q={searchQuery}", requests.Last().AbsoluteUrl);Assert.Contains($"/search?q={searchQuery}", requests.First().AbsoluteUrl);}protected override Dictionary<string, string> GetConfiguration(){_mockServerSearchEngine = FluentMockServer.Start();var googleUrl = _mockServerSearchEngine.Urls.Single();var configuration = base.GetConfiguration();configuration.Add("Google", googleUrl);return configuration;}protected override void Dispose(bool disposing){base.Dispose(disposing);if (disposing){_mockServerSearchEngine.Stop();_mockServerSearchEngine.Dispose();}}private void SetupStableServer(string response){_mockServerSearchEngine.Given(Request.Create().UsingGet()).RespondWith(Response.Create().WithBody(response, encoding:Encoding.UTF8).WithStatusCode(HttpStatusCode.OK));}private void SetupUnStableServer(string response){_mockServerSearchEngine.Given(Request.Create().UsingGet()).InScenario("UnstableServer").WillSetStateTo("FIRSTCALLDONE").RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8).WithStatusCode(HttpStatusCode.InternalServerError));_mockServerSearchEngine.Given(Request.Create().UsingGet()).InScenario("UnstableServer").WhenStateIs("FIRSTCALLDONE").RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8).WithStatusCode(HttpStatusCode.OK));}
}
Web应用程序和外部服务都是自托管的。无需启动其中之一。我们像进行单元测试一样进行测试。这就是方法的作用:
- SetupStableServer:我们设置了一个模拟的外部服务,并确保它的行为像一个稳定的服务。它总是返回状态码为200的响应。
- SetupUnStableServer:这是设置一个模拟的外部服务,在第一个请求失败后返回 200(500,内部服务器错误)
- Dispose: 停止对外服务
- GetConfiguration:返回新的配置设置。我们使用具有不同 (localhost) url的模拟外部服务。
- TestWithStableServer: 用稳定的服务器测试。我们调用我们自己的服务并验证我们自己的服务发送的请求(它必须是一个)是正确的。
- TestWithUnstableServer:一种非常相似的方法,但由于外部服务的行为不稳定,因此预计会发送两个请求,我们有重试策略来处理这种情况。
兴趣点
有关于.NET Core集成测试的很好的文档。还有很好的文档关于WireMock.NET。我刚刚解释了如何结合这些技术,这确实是一个不同且被低估的主题。集成测试是实现良好代码覆盖率的一种非常好的方式,通过REST调用测试应用程序而无需托管和部署,并使测试真实,因为不需要模拟内部依赖项。但是,仍然需要模拟外部依赖项。否则,测试失败并不意味着你自己的应用程序(外部应用程序可能宕机),成功也不意味着这么多(它可能无法处理外部服务的意外故障)。因此,WireMock.NET可以帮助您。它使您的测试更有意义。
如果您对完整的源代码感兴趣,可以在GitHub上找到。
https://www.codeproject.com/Articles/5267354/How-WireMock-NET-Can-Help-in-Doing-Integration-Tes
WireMock.NET如何帮助进行.NET Core应用程序的集成测试相关推荐
- 具有数据库依赖性的.NET Core应用程序的集成测试
目录 介绍 背景 使用代码 兴趣点 简要说明了.NET Core数据库测试存在的问题.随后,通过GitHub上的具体代码示例说明了解决方案. 介绍 对具有数据库依赖性的应用程序进行自动测试是一项艰巨的 ...
- 在ASP.NET Core应用程序中使用分布式缓存
本文要点 ASP.NET Core内置了分布式缓存接口. 分布式缓存的主要好处有性能.数据共享和稳定性. Couchbase服务器是一个内存优先的数据库,非常适合作为分布式缓存. NuGet程序包使得 ...
- netcore更新dll要停止_使 .NET Core 应用程序容器化
在本教程中,你将了解如何使用 Docker 容器化 .NET Core 应用. 容器具有很多特性和优点,如具有不可变的基础结构.提供可移植的体系结构和实现可伸缩性. 此影像可用于为本地开发环境.私有云 ...
- 【实验手册】使用Visual Studio Code 开发.NET Core应用程序
.NET Core with Visual Studio Code 目录 概述... 2 先决条件... 2 练习1: 安装和配置.NET Core以及Visual Studio Code 扩展... ...
- 运行Vue在ASP.NET Core应用程序并部署在IIS上
前言 从.NET Core 1.0开始我们就将其应用到项目中,但是呢我对ASP.NET Core一些原理也还未开始研究,仅限于会用,不过园子中已有大量文章存在,借着有点空余时间,我们来讲讲如何利用AS ...
- 使用插件创建 .NET Core 应用程序
使用插件创建 .NET Core 应用程序 本教程展示了如何创建自定义的 AssemblyLoadContext 来加载插件.AssemblyDependencyResolver 用于解析插件的 ...
- 如何使用 HttpReports 监控 .NET Core 应用程序
简介 HttpReports 基于.NET Core 开发的APM监控系统,使用MIT开源协议,主要功能包括,统计, 分析, 可视化, 监控,追踪等,适合在中小项目中使用. github:https: ...
- 5种设置ASP.NET Core应用程序URL的方法
默认情况下,ASP.NET Core应用程序监听以下URL: •http://localhost:5000•https://localhost:5001 在这篇文章中,我展示了5种不同的方式来更改您的 ...
- 将终结点图添加到你的ASP.NET Core应用程序中
在本文中,我将展示如何使用DfaGraphWriter服务在ASP.NET Core 3.0应用程序中可视化你的终结点路由.上面文章我向您演示了如何生成一个有向图(如我上篇文章[译]使用DOT语言和G ...
最新文章
- PAT(甲级)2019年冬季考试 7-4 Cartesian Tree
- Nature子刊:三代Nonopore测序数据耐药性分析软件NanoOK RT
- 深度学习在gilt应用——用图像相似性搜索引擎来商品推荐和服务属性分类
- 儿童手工制作日历_怎么做手工儿童卡通绵羊日程管理小日历
- python一行没写完用什么隔离_完全隔离的Python环境
- 面试题热个身:5 亿整数的大文件,来排个序?
- 通过阿里云容器镜像服务海外服务器构建spark-operator镜像
- linux脚本写的计算器,一步步打造自己的linux命令行计算器
- go语言实战_字节跳动年薪50W抢Go开发人才,你还在问该不该学?
- (30)FPGA米勒型状态机设计(一段式)(第6天)
- javascript实现窗口随着鼠标移动且移动路径重现
- TCP/IP 协议大致的概念
- Angular 分页
- python遗传算法最短路径问题有几种类型_用遗传算法求解最短路径问题.pdf
- 西门子 1200PLC全额补贴课程
- 如何掌握苹果发布会 PPT 制作要点
- 双光耦开关电源电路图_简单的开关电源电路图大全(六款简单的开关电源电路设计原理图详解)...
- JDR与JRE的区别
- HYSBZ 4198 荷马史诗
- 什么是人机交互技术?
热门文章
- c语言飞扬的小鸟程序,C语言实现flappy bird游戏
- 匿名内部类 可以访问外部类_Java 内部类与外部类的互访使用小结
- nginx反代web页面没有正常显示_web漏洞-SSI注入漏洞深入详解
- css怎么让div旋转不改变形状,旋转任意角度 如何让div旋转一定的角度
- java map遍历_Java中Map集合的两种遍历方式
- rx2700_第二代锐龙 7 2700X 台式处理器 | AMD
- 难以拒绝的中国风雅致新年元旦海报来袭
- hashmap是单向链表吗_HashMap源码大剖析
- 如何判断lib是/md or /mt编译的
- Windows错误代码转换成文字信息描述