目录

介绍

背景

使用代码

兴趣点


  • 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应用程序的集成测试相关推荐

  1. 具有数据库依赖性的.NET Core应用程序的集成测试

    目录 介绍 背景 使用代码 兴趣点 简要说明了.NET Core数据库测试存在的问题.随后,通过GitHub上的具体代码示例说明了解决方案. 介绍 对具有数据库依赖性的应用程序进行自动测试是一项艰巨的 ...

  2. 在ASP.NET Core应用程序中使用分布式缓存

    本文要点 ASP.NET Core内置了分布式缓存接口. 分布式缓存的主要好处有性能.数据共享和稳定性. Couchbase服务器是一个内存优先的数据库,非常适合作为分布式缓存. NuGet程序包使得 ...

  3. netcore更新dll要停止_使 .NET Core 应用程序容器化

    在本教程中,你将了解如何使用 Docker 容器化 .NET Core 应用. 容器具有很多特性和优点,如具有不可变的基础结构.提供可移植的体系结构和实现可伸缩性. 此影像可用于为本地开发环境.私有云 ...

  4. 【实验手册】使用Visual Studio Code 开发.NET Core应用程序

    .NET Core with Visual Studio Code 目录 概述... 2 先决条件... 2 练习1: 安装和配置.NET Core以及Visual Studio Code 扩展... ...

  5. 运行Vue在ASP.NET Core应用程序并部署在IIS上

    前言 从.NET Core 1.0开始我们就将其应用到项目中,但是呢我对ASP.NET Core一些原理也还未开始研究,仅限于会用,不过园子中已有大量文章存在,借着有点空余时间,我们来讲讲如何利用AS ...

  6. 使用插件创建 .NET Core 应用程序

    使用插件创建 .NET Core 应用程序 本教程展示了如何创建自定义的  AssemblyLoadContext  来加载插件.AssemblyDependencyResolver  用于解析插件的 ...

  7. 如何使用 HttpReports 监控 .NET Core 应用程序

    简介 HttpReports 基于.NET Core 开发的APM监控系统,使用MIT开源协议,主要功能包括,统计, 分析, 可视化, 监控,追踪等,适合在中小项目中使用. github:https: ...

  8. 5种设置ASP.NET Core应用程序URL的方法

    默认情况下,ASP.NET Core应用程序监听以下URL: •http://localhost:5000•https://localhost:5001 在这篇文章中,我展示了5种不同的方式来更改您的 ...

  9. 将终结点图添加到你的ASP.NET Core应用程序中

    在本文中,我将展示如何使用DfaGraphWriter服务在ASP.NET Core 3.0应用程序中可视化你的终结点路由.上面文章我向您演示了如何生成一个有向图(如我上篇文章[译]使用DOT语言和G ...

最新文章

  1. PAT(甲级)2019年冬季考试 7-4 Cartesian Tree
  2. Nature子刊:三代Nonopore测序数据耐药性分析软件NanoOK RT
  3. 深度学习在gilt应用——用图像相似性搜索引擎来商品推荐和服务属性分类
  4. 儿童手工制作日历_怎么做手工儿童卡通绵羊日程管理小日历
  5. python一行没写完用什么隔离_完全隔离的Python环境
  6. 面试题热个身:5 亿整数的大文件,来排个序?
  7. 通过阿里云容器镜像服务海外服务器构建spark-operator镜像
  8. linux脚本写的计算器,一步步打造自己的linux命令行计算器
  9. go语言实战_字节跳动年薪50W抢Go开发人才,你还在问该不该学?
  10. (30)FPGA米勒型状态机设计(一段式)(第6天)
  11. javascript实现窗口随着鼠标移动且移动路径重现
  12. TCP/IP 协议大致的概念
  13. Angular 分页
  14. python遗传算法最短路径问题有几种类型_用遗传算法求解最短路径问题.pdf
  15. 西门子 1200PLC全额补贴课程
  16. 如何掌握苹果发布会 PPT 制作要点
  17. 双光耦开关电源电路图_简单的开关电源电路图大全(六款简单的开关电源电路设计原理图详解)...
  18. JDR与JRE的区别
  19. HYSBZ 4198 荷马史诗
  20. 什么是人机交互技术?

热门文章

  1. c语言飞扬的小鸟程序,C语言实现flappy bird游戏
  2. 匿名内部类 可以访问外部类_Java 内部类与外部类的互访使用小结
  3. nginx反代web页面没有正常显示_web漏洞-SSI注入漏洞深入详解
  4. css怎么让div旋转不改变形状,旋转任意角度 如何让div旋转一定的角度
  5. java map遍历_Java中Map集合的两种遍历方式
  6. rx2700_第二代锐龙 7 2700X 台式处理器 | AMD
  7. 难以拒绝的中国风雅致新年元旦海报来袭
  8. hashmap是单向链表吗_HashMap源码大剖析
  9. 如何判断lib是/md or /mt编译的
  10. Windows错误代码转换成文字信息描述