{"id":1306,"date":"2026-03-21T11:37:34","date_gmt":"2026-03-21T14:37:34","guid":{"rendered":"https:\/\/maurobernal.com.ar\/blog\/?p=1306"},"modified":"2026-03-24T12:20:49","modified_gmt":"2026-03-24T15:20:49","slug":"testea-apis-externas-sin-internet-httpclientfactory-moq-explicados","status":"publish","type":"post","link":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/","title":{"rendered":"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados"},"content":{"rendered":"\n<p>Cuando trabaj\u00e1s con aplicaciones .NET que consumen APIs externas, tarde o temprano te encontr\u00e1s con este dilema: \u00bfc\u00f3mo escrib\u00eds unit tests que no dependan de que esa API est\u00e9 disponible? La respuesta est\u00e1 en combinar <strong>HttpClientFactory<\/strong> con <strong>Moq<\/strong> para simular las respuestas HTTP de forma completamente aislada.<\/p>\n\n\n\n<p>En este art\u00edculo vamos a ver por qu\u00e9 no pod\u00e9s simplemente hacer <code>new Mock&lt;IHttpClientFactory&gt;()<\/code> y salir andando, y c\u00f3mo resolver el problema de forma elegante.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfPor qu\u00e9 HttpClientFactory y no HttpClient directo?<\/h2>\n\n\n\n<p>Antes de entrar al testing, un poco de contexto. Instanciar <code>HttpClient<\/code> directamente con <code>new HttpClient()<\/code> en cada request es una pr\u00e1ctica peligrosa: puede causar <em>socket exhaustion<\/em> porque el sistema operativo no libera los sockets r\u00e1pidamente. <code>IHttpClientFactory<\/code> resuelve este problema manejando el pool de conexiones por vos.<\/p>\n\n\n\n<p>El patr\u00f3n t\u00edpico se ve as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public class MyExternalService\n{\n    private readonly IHttpClientFactory _httpClientFactory;\n\n    public MyExternalService(IHttpClientFactory httpClientFactory)\n    {\n        _httpClientFactory = httpClientFactory;\n    }\n\n    public async Task DeleteObject(string objectName)\n    {\n        string path = $\"\/objects?name={objectName}\";\n        var client = _httpClientFactory.CreateClient(\"ext_service\");\n\n        var httpResponse = await client.DeleteAsync(path);\n        httpResponse.EnsureSuccessStatusCode();\n    }\n}<\/code><\/pre>\n\n\n\n<p>El problema: \u00bfc\u00f3mo testear esto sin hacer una llamada HTTP real?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El problema con el mocking directo<\/h2>\n\n\n\n<p>El instinto natural ser\u00eda hacer esto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">var mockFactory = new Mock&lt;IHttpClientFactory&gt;();\n\/\/ ... y ya?<\/code><\/pre>\n\n\n\n<p>El inconveniente es que <code>HttpClient<\/code> no tiene una interfaz mockeable directamente para sus operaciones HTTP. Lo que realmente env\u00eda las requests es el <code>HttpMessageHandler<\/code> que vive adentro del cliente. Y encima, el m\u00e9todo clave \u2014 <code>SendAsync<\/code> \u2014 es <strong>protected<\/strong>.<\/p>\n\n\n\n<p>Por eso necesitamos el combo: mockear el handler, construir un cliente con ese handler, y reci\u00e9n despu\u00e9s mockear la factory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La soluci\u00f3n: mockear HttpMessageHandler con Moq<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Paso 1 \u2014 Crear el mock del handler<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">var handlerMock = new Mock&lt;HttpMessageHandler&gt;(MockBehavior.Strict);<\/code><\/pre>\n\n\n\n<p><code>MockBehavior.Strict<\/code> hace que el mock lance una excepci\u00f3n si se llama a un m\u00e9todo que no fue configurado expl\u00edcitamente. Es una buena pr\u00e1ctica: si tu test pasa silenciosamente pero hay llamadas no esperadas, Strict te avisa.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Paso 2 \u2014 Definir el comportamiento de SendAsync<\/h3>\n\n\n\n<p>Ac\u00e1 entra la magia de <code>.Protected()<\/code>. Como <code>SendAsync<\/code> es un m\u00e9todo <code>protected internal abstract<\/code>, no pod\u00e9s accederlo directamente. Moq te da una puerta trasera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">var fakeResponse = new HttpResponseMessage\n{\n    StatusCode = HttpStatusCode.OK,\n    Content = new StringContent(\"{\\\"id\\\":1,\\\"name\\\":\\\"Producto de prueba\\\"}\", \n                                Encoding.UTF8, \"application\/json\")\n};\n\nhandlerMock\n    .Protected()\n    .Setup&lt;Task&lt;HttpResponseMessage&gt;&gt;(\n        \"SendAsync\",\n        ItExpr.IsAny&lt;HttpRequestMessage&gt;(),\n        ItExpr.IsAny&lt;CancellationToken&gt;()\n    )\n    .ReturnsAsync(fakeResponse)\n    .Verifiable();<\/code><\/pre>\n\n\n\n<p>Con <code>ItExpr.IsAny&lt;&gt;()<\/code> capturamos cualquier request. Si necesit\u00e1s ser m\u00e1s espec\u00edfico (verificar que se llam\u00f3 a una URL en particular), pod\u00e9s usar <code>ItExpr.Is&lt;HttpRequestMessage&gt;<\/code> con una condici\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">ItExpr.Is&lt;HttpRequestMessage&gt;(req =&gt;\n    req.Method == HttpMethod.Delete &&\n    req.RequestUri!.PathAndQuery.Contains(\"name=producto-test\")\n)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Paso 3 \u2014 Crear el HttpClient con ese handler<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">var httpClient = new HttpClient(handlerMock.Object)\n{\n    BaseAddress = new Uri(\"https:\/\/api.miservicio.com\/\")\n};<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Paso 4 \u2014 Mockear IHttpClientFactory<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">var mockFactory = new Mock&lt;IHttpClientFactory&gt;();\nmockFactory\n    .Setup(f =&gt; f.CreateClient(\"ext_service\"))\n    .Returns(httpClient);<\/code><\/pre>\n\n\n\n<p>Importante: el string <code>\"ext_service\"<\/code> tiene que coincidir exactamente con el nombre que usa tu servicio al llamar <code>CreateClient()<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Paso 5 \u2014 Armar el test completo (patr\u00f3n AAA)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">[Fact]\npublic async Task DeleteObject_DeberiaLlamarAlEndpointCorrecto()\n{\n    \/\/ Arrange\n    var handlerMock = new Mock&lt;HttpMessageHandler&gt;(MockBehavior.Strict);\n\n    handlerMock\n        .Protected()\n        .Setup&lt;Task&lt;HttpResponseMessage&gt;&gt;(\n            \"SendAsync\",\n            ItExpr.IsAny&lt;HttpRequestMessage&gt;(),\n            ItExpr.IsAny&lt;CancellationToken&gt;()\n        )\n        .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK })\n        .Verifiable();\n\n    var httpClient = new HttpClient(handlerMock.Object)\n    {\n        BaseAddress = new Uri(\"https:\/\/api.miservicio.com\/\")\n    };\n\n    var mockFactory = new Mock&lt;IHttpClientFactory&gt;();\n    mockFactory.Setup(f =&gt; f.CreateClient(\"ext_service\")).Returns(httpClient);\n\n    var service = new MyExternalService(mockFactory.Object);\n\n    \/\/ Act\n    await service.DeleteObject(\"producto-test\");\n\n    \/\/ Assert\n    handlerMock.Protected().Verify(\n        \"SendAsync\",\n        Times.Once(),\n        ItExpr.Is&lt;HttpRequestMessage&gt;(req =&gt;\n            req.Method == HttpMethod.Delete &&\n            req.RequestUri!.PathAndQuery.Contains(\"name=producto-test\")\n        ),\n        ItExpr.IsAny&lt;CancellationToken&gt;()\n    );\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Typed Clients: el caso m\u00e1s simple<\/h2>\n\n\n\n<p>Si us\u00e1s <strong>Typed Clients<\/strong> (el patr\u00f3n donde el <code>HttpClient<\/code> se inyecta directamente en el constructor de tu clase), el setup es incluso m\u00e1s directo: no necesit\u00e1s mockear <code>IHttpClientFactory<\/code> en absoluto.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">public class ProductoApiClient\n{\n    private readonly HttpClient _client;\n\n    public ProductoApiClient(HttpClient client)\n    {\n        _client = client;\n    }\n\n    public async Task&lt;string&gt; GetProductoAsync(int id)\n    {\n        var response = await _client.GetAsync($\"\/api\/productos\/{id}\");\n        response.EnsureSuccessStatusCode();\n        return await response.Content.ReadAsStringAsync();\n    }\n}\n\n\/\/ En el test:\n[Fact]\npublic async Task GetProducto_DeberiaRetornarJson()\n{\n    \/\/ Arrange\n    var handlerMock = new Mock&lt;HttpMessageHandler&gt;();\n    handlerMock\n        .Protected()\n        .Setup&lt;Task&lt;HttpResponseMessage&gt;&gt;(\n            \"SendAsync\",\n            ItExpr.IsAny&lt;HttpRequestMessage&gt;(),\n            ItExpr.IsAny&lt;CancellationToken&gt;()\n        )\n        .ReturnsAsync(new HttpResponseMessage\n        {\n            StatusCode = HttpStatusCode.OK,\n            Content = new StringContent(\"{\\\"id\\\":1,\\\"nombre\\\":\\\"Laptop\\\"}\")\n        });\n\n    var httpClient = new HttpClient(handlerMock.Object)\n    {\n        BaseAddress = new Uri(\"https:\/\/api.productos.com\/\")\n    };\n\n    var client = new ProductoApiClient(httpClient);\n\n    \/\/ Act\n    var resultado = await client.GetProductoAsync(1);\n\n    \/\/ Assert\n    Assert.Contains(\"Laptop\", resultado);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Alternativa: Moq.Contrib.HttpClient<\/h2>\n\n\n\n<p>Si el setup te parece verboso (y con raz\u00f3n), existe el paquete <code>Moq.Contrib.HttpClient<\/code> que simplifica bastante la sintaxis:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"csharp\" class=\"language-csharp\">\/\/ Install: dotnet add package Moq.Contrib.HttpClient\n\nvar handlerMock = new Mock&lt;HttpMessageHandler&gt;();\n\nhandlerMock\n    .SetupRequest(HttpMethod.Get, \"https:\/\/api.productos.com\/api\/productos\/1\")\n    .ReturnsResponse(HttpStatusCode.OK, \"{\\\"id\\\":1,\\\"nombre\\\":\\\"Laptop\\\"}\", \"application\/json\");\n\nvar httpClient = handlerMock.CreateClient();  \/\/ O handlerMock.CreateClientFactory() para IHttpClientFactory\nvar client = new ProductoApiClient(httpClient);\n\nvar resultado = await client.GetProductoAsync(1);\n\n\/\/ Verificaci\u00f3n tambi\u00e9n m\u00e1s limpia:\nhandlerMock.VerifyRequest(HttpMethod.Get, \"https:\/\/api.productos.com\/api\/productos\/1\", Times.Once());<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Resumen: \u00bfcu\u00e1ndo usar cada enfoque?<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Patr\u00f3n<\/th><th>C\u00f3mo testear<\/th><\/tr><\/thead><tbody><tr><td>Named Client (IHttpClientFactory)<\/td><td>Mock handler \u2192 HttpClient \u2192 Mock Factory<\/td><\/tr><tr><td>Typed Client (HttpClient inyectado)<\/td><td>Mock handler \u2192 HttpClient \u2192 inyectar directo<\/td><\/tr><tr><td>Quiero menos boilerplate<\/td><td>Moq.Contrib.HttpClient<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusi\u00f3n<\/h2>\n\n\n\n<p>La clave est\u00e1 en entender que <code>HttpClient<\/code> delega el trabajo real a <code>HttpMessageHandler<\/code>, y ese handler es el punto donde interceptamos las llamadas HTTP en los tests. Una vez que ten\u00e9s ese concepto claro, el resto es mec\u00e1nica.<\/p>\n\n\n\n<p>El resultado: tests r\u00e1pidos, deterministas, que no fallan porque la API externa est\u00e1 ca\u00edda o porque el ambiente de CI no tiene acceso a internet. Exactamente lo que necesit\u00e1s para un pipeline de CI\/CD confiable.<\/p>\n\n\n\n<p><strong>\u00bfUs\u00e1s otro approach para mockear HTTP en tus tests? Dejalo en los comentarios.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>C\u00f3mo escribir unit tests aislados y deterministas para servicios .NET que usan IHttpClientFactory, usando Moq para mockear el HttpMessageHandler.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1],"tags":[38,39,18,308,283,322],"class_list":["post-1306","post","type-post","status-publish","format-standard","hentry","category-blog","tag-net","tag-c","tag-dotnet","tag-httpclient","tag-testing","tag-web-api"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados &#183; devops Mauro Bernal<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados &#183; devops Mauro Bernal\" \/>\n<meta property=\"og:description\" content=\"C\u00f3mo escribir unit tests aislados y deterministas para servicios .NET que usan IHttpClientFactory, usando Moq para mockear el HttpMessageHandler.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/\" \/>\n<meta property=\"og:site_name\" content=\"devops Mauro Bernal\" \/>\n<meta property=\"article:published_time\" content=\"2026-03-21T14:37:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-24T15:20:49+00:00\" \/>\n<meta name=\"author\" content=\"Mauro Bernal\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@_maurobernal\" \/>\n<meta name=\"twitter:site\" content=\"@_maurobernal\" \/>\n<meta name=\"twitter:label1\" content=\"Escrito por\" \/>\n\t<meta name=\"twitter:data1\" content=\"Mauro Bernal\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/\"},\"author\":{\"name\":\"Mauro Bernal\",\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/#\\\/schema\\\/person\\\/09c4dbdfb59b20e015c703fd19713283\"},\"headline\":\"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados\",\"datePublished\":\"2026-03-21T14:37:34+00:00\",\"dateModified\":\"2026-03-24T15:20:49+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/\"},\"wordCount\":556,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/#\\\/schema\\\/person\\\/09c4dbdfb59b20e015c703fd19713283\"},\"keywords\":[\".NET\",\"c#\",\"dotnet\",\"httpClient\",\"testing\",\"web-api\"],\"articleSection\":[\"Blog\"],\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/\",\"url\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/\",\"name\":\"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados &#183; devops Mauro Bernal\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/#website\"},\"datePublished\":\"2026-03-21T14:37:34+00:00\",\"dateModified\":\"2026-03-24T15:20:49+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/\",\"name\":\"devops Mauro Bernal\",\"description\":\"Cuando tu trabajo es hacer que las cosas funcionen bien...\",\"publisher\":{\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/#\\\/schema\\\/person\\\/09c4dbdfb59b20e015c703fd19713283\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/maurobernal.com.ar\\\/blog\\\/#\\\/schema\\\/person\\\/09c4dbdfb59b20e015c703fd19713283\",\"name\":\"Mauro Bernal\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/i0.wp.com\\\/maurobernal.com.ar\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/logo-maurobernal.png?fit=1740%2C1740&ssl=1\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/maurobernal.com.ar\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/logo-maurobernal.png?fit=1740%2C1740&ssl=1\",\"contentUrl\":\"https:\\\/\\\/i0.wp.com\\\/maurobernal.com.ar\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/logo-maurobernal.png?fit=1740%2C1740&ssl=1\",\"width\":1740,\"height\":1740,\"caption\":\"Mauro Bernal\"},\"logo\":{\"@id\":\"https:\\\/\\\/i0.wp.com\\\/maurobernal.com.ar\\\/blog\\\/wp-content\\\/uploads\\\/2023\\\/07\\\/logo-maurobernal.png?fit=1740%2C1740&ssl=1\"},\"description\":\"Desarrollo de Sistemas en .Net, IT Callcenters, DBA de SQL Server, Mikrotik, Pentest y T\u00e9cnico consultor de Sistemas Bejerman\",\"sameAs\":[\"https:\\\/\\\/maurobernal.com.ar\",\"https:\\\/\\\/x.com\\\/_maurobernal\",\"https:\\\/\\\/youtube.com\\\/maurobernal\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados &#183; devops Mauro Bernal","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/","og_locale":"es_ES","og_type":"article","og_title":"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados &#183; devops Mauro Bernal","og_description":"C\u00f3mo escribir unit tests aislados y deterministas para servicios .NET que usan IHttpClientFactory, usando Moq para mockear el HttpMessageHandler.","og_url":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/","og_site_name":"devops Mauro Bernal","article_published_time":"2026-03-21T14:37:34+00:00","article_modified_time":"2026-03-24T15:20:49+00:00","author":"Mauro Bernal","twitter_card":"summary_large_image","twitter_creator":"@_maurobernal","twitter_site":"@_maurobernal","twitter_misc":{"Escrito por":"Mauro Bernal","Tiempo de lectura":"5 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/#article","isPartOf":{"@id":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/"},"author":{"name":"Mauro Bernal","@id":"https:\/\/maurobernal.com.ar\/blog\/#\/schema\/person\/09c4dbdfb59b20e015c703fd19713283"},"headline":"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados","datePublished":"2026-03-21T14:37:34+00:00","dateModified":"2026-03-24T15:20:49+00:00","mainEntityOfPage":{"@id":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/"},"wordCount":556,"commentCount":0,"publisher":{"@id":"https:\/\/maurobernal.com.ar\/blog\/#\/schema\/person\/09c4dbdfb59b20e015c703fd19713283"},"keywords":[".NET","c#","dotnet","httpClient","testing","web-api"],"articleSection":["Blog"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/","url":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/","name":"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados &#183; devops Mauro Bernal","isPartOf":{"@id":"https:\/\/maurobernal.com.ar\/blog\/#website"},"datePublished":"2026-03-21T14:37:34+00:00","dateModified":"2026-03-24T15:20:49+00:00","breadcrumb":{"@id":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/maurobernal.com.ar\/blog\/testea-apis-externas-sin-internet-httpclientfactory-moq-explicados\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/maurobernal.com.ar\/blog\/"},{"@type":"ListItem","position":2,"name":"Teste\u00e1 APIs externas sin internet: HttpClientFactory + Moq explicados"}]},{"@type":"WebSite","@id":"https:\/\/maurobernal.com.ar\/blog\/#website","url":"https:\/\/maurobernal.com.ar\/blog\/","name":"devops Mauro Bernal","description":"Cuando tu trabajo es hacer que las cosas funcionen bien...","publisher":{"@id":"https:\/\/maurobernal.com.ar\/blog\/#\/schema\/person\/09c4dbdfb59b20e015c703fd19713283"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/maurobernal.com.ar\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":["Person","Organization"],"@id":"https:\/\/maurobernal.com.ar\/blog\/#\/schema\/person\/09c4dbdfb59b20e015c703fd19713283","name":"Mauro Bernal","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/i0.wp.com\/maurobernal.com.ar\/blog\/wp-content\/uploads\/2023\/07\/logo-maurobernal.png?fit=1740%2C1740&ssl=1","url":"https:\/\/i0.wp.com\/maurobernal.com.ar\/blog\/wp-content\/uploads\/2023\/07\/logo-maurobernal.png?fit=1740%2C1740&ssl=1","contentUrl":"https:\/\/i0.wp.com\/maurobernal.com.ar\/blog\/wp-content\/uploads\/2023\/07\/logo-maurobernal.png?fit=1740%2C1740&ssl=1","width":1740,"height":1740,"caption":"Mauro Bernal"},"logo":{"@id":"https:\/\/i0.wp.com\/maurobernal.com.ar\/blog\/wp-content\/uploads\/2023\/07\/logo-maurobernal.png?fit=1740%2C1740&ssl=1"},"description":"Desarrollo de Sistemas en .Net, IT Callcenters, DBA de SQL Server, Mikrotik, Pentest y T\u00e9cnico consultor de Sistemas Bejerman","sameAs":["https:\/\/maurobernal.com.ar","https:\/\/x.com\/_maurobernal","https:\/\/youtube.com\/maurobernal"]}]}},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/posts\/1306","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/comments?post=1306"}],"version-history":[{"count":1,"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/posts\/1306\/revisions"}],"predecessor-version":[{"id":1307,"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/posts\/1306\/revisions\/1307"}],"wp:attachment":[{"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/media?parent=1306"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/categories?post=1306"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/maurobernal.com.ar\/blog\/wp-json\/wp\/v2\/tags?post=1306"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}