Giter Site home page Giter Site logo

Comments (13)

maxkagamine avatar maxkagamine commented on May 13, 2024

Parece que estás usando un named client, ¿no? Si el constructor del servicio de Datos Basicos recibe un IHttpClientFactory, puedes configurar un mock que simula tu ejemplo como esto:

var handler = new Mock<HttpMessageHandler>();
var factory = handler.CreateClientFactory();

Mock.Get(factory).Setup(x => x.CreateClient(NamesEndPointConstants.DatosBasicos))
    .Returns(() =>
    {
        var client = handler.CreateClient();
        client.BaseAddress = new Uri(UrlDatosBasicos); // Tal vez una constante en la prueba unitaria
        return client;
    });

var datosBasicos = new DatosBasicosServicio(factory);

// o si usas AutoMocker:
mocker.Use<IHttpClientFactory>(factory);
var datosBasicos = mocker.CreateInstance<DatosBasicosServicio>();

Pero si recibe un HttpClient, puedes simplemente hacer esto:

var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();

client.BaseAddress = new Uri(UrlDatosBasicos);

Porque el retry policy no es un trabajo del métodos de servicio, creo no es necesario aquí. Quieres probar retry, yo escribiría una prueba unitaria para GetRetryPolicy(). Por favor ves aquí para un ejemplo.

Espero que esto ayude. (Mi español no es bueno, lo siento.)

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Yo quiero usarlo pero en un TestServer. Haciendo pruebas a la web api.

Mi Inyección real en el startup, es esta.

services.AddHttpClient(NamesEndPointConstants.IdentityServer, config =>
            {
                config.BaseAddress = new Uri(configuration.GetOicAuthority());
            }).SetHandlerLifetime(TimeSpan.FromMinutes(5))
             .AddPolicyHandler(GetRetryPolicy());

Quiero hacer que el Mock para reemplazar esa configuración. Me es útil para que mi sistema no se comunique con sistemas externos.
En la siguiente la clase en factory.ConfigureTestServices(services =>, puede reemplazar inyecciones de dependencia.

Si tienes alguna idea sera muy bueno.

En el momento me toco hacer Mock al servicio externo, pero me gustaria burla al HttpClient y que tener una mayor cobertura de pruebas.

Si puedes ayudarme, excelente.

public class ClaseDescuentoControllerTest : IClassFixture<ApiWebApplicationFactory>
    {
        private readonly HttpClient _client;

        public ClaseDescuentoControllerTest(ApiWebApplicationFactory factory)
        {
            factory.ConfigureTestServices(services =>
            {
                var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IConsultarTerceroDatosBasicosService));
                services.Remove(descriptor);

                var mockConsultarTerceroDatosBasicosServiceHttp = Mock.Of<IConsultarTerceroDatosBasicosService>();
                Mock.Get(mockConsultarTerceroDatosBasicosServiceHttp)
                .Setup(m => m.Consultar(It.IsAny<string>()))
                .Returns(new ConsultarTerceroDatosBasicosServiceResponse(new TerceroModelView() { Identificacion = "1003195636" }));

                services.AddTransient<IConsultarTerceroDatosBasicosService>(sp => mockConsultarTerceroDatosBasicosServiceHttp);
            });

            _client = factory.CreateClient();
        }

from moq.contrib.httpclient.

maxkagamine avatar maxkagamine commented on May 13, 2024

¡Ah, es pruebas de integración! Lo siento, ahora entiendo.

Yo envió una muestra de proyecto de ASP.NET Core y de prueba de integración:

test/IntegrationTestExample/Startup.cs
test/IntegrationTestExample.Test/ExampleTests.cs

Creo que quieres algo como esto:

public class ClaseDescuentoControllerTest : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly WebApplicationFactory<Startup> _factory;
    private readonly Mock<HttpMessageHandler> _datosBasicosHandler = new();

    public ClaseDescuentoControllerTest(WebApplicationFactory<Startup> factory)
    {
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureTestServices(services =>
            {
                services.AddHttpClient(NamesEndPointConstants.DatosBasicos)
                    .ConfigurePrimaryHttpMessageHandler(() => _datosBasicosHandler.Object);
            });
        });
    }

    // ...
}

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Buenas noche, no me funciona.
Al parecer si inyecta el mock, pero no funciona correctamente.

One or more errors occurred. (Object reference not set to an instance of an object.) al crear el cliente var resultado = response.Result;// here

Thank you!!

       public ConsultarTerceroDatosBasicosServiceResponse Consultar(string identificacion)
        {
            var apiClient = _factoryHttp.Crear(NamesEndPointConstants.DatosBasicos);
    
            var response = apiClient.Result.GetAsync($"api/Consulta/Tercero/{identificacion}");
            var resultado = response.Result;// here
            if (!response.Result.IsSuccessStatusCode)
            {
                return new ConsultarTerceroDatosBasicosServiceResponse(null);
            }
            else
            {
                var respuesta = response.Result.Content.ReadAsStringAsync().Result;
                var responseAgent = JsonConvert.DeserializeObject<TerceroModelView>(respuesta);
                return new ConsultarTerceroDatosBasicosServiceResponse(responseAgent);
            }
        }

Test

        [Fact]
        public async Task GetClaseDescuentoRegistroTestAsync()
        {
            TerceroModelView terceroEntidad = new () { Identificacion = "1003195636" };

            // Notice there was no need to set a BaseAddress in this test class. All of the dependency injection and the
            // AddHttpClient() in ConfigureServices() are essentially unchanged and working as they would normally.
            _datosBasicosHandler.SetupRequest(HttpMethod.Get, $"https://signusfinanciero/datosbasicos/api/Consulta/Tercero/{terceroEntidad.Identificacion}")
                .ReturnsResponse(JsonConvert.SerializeObject(terceroEntidad), "application/json");
            
            var client = _factory.CreateClient();
            var roles = new List<string>() { "ContabilidadApi_Integracion" };
            var token = JwtService.GenerateToken("boris", roles);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
            var vigencia = 2020;
            var result = await client.GetAsync($"/api/ClaseDescuentoRegistro/DatosFormulario/{vigencia}");
            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
        }

Config test

            factory.ConfigureTestServices(services =>
            {
                /* REEMPLAZA EL SERVICIO
                var mockConsultarTerceroDatosBasicosServiceHttp = Mock.Of<IConsultarTerceroDatosBasicosService>();
                Mock.Get(mockConsultarTerceroDatosBasicosServiceHttp)
                .Setup(m => m.Consultar(It.IsAny<string>()))
                .Returns(new ConsultarTerceroDatosBasicosServiceResponse(new TerceroModelView() { Identificacion = "1003195636" }));
                services.AddTransient<IConsultarTerceroDatosBasicosService>(sp => mockConsultarTerceroDatosBasicosServiceHttp);*/

                //REEMPLAZA LA PETICION EXTERNA
                services.AddHttpClient(NamesEndPointConstants.DatosBasicos)
                    .ConfigurePrimaryHttpMessageHandler(() => _datosBasicosHandler.Object);

            });

from moq.contrib.httpclient.

maxkagamine avatar maxkagamine commented on May 13, 2024

Si he entendido bien, _factoryHttp.Crear() es un async wrapper de IHttpClientFactory.CreateClient() y apiClient es decididamente un HttpClient que usa el mock handler, ¿no? Pero response es null.

Primero, .Result es blocking y propenso a los errores. Si no puedes hacer que Consultar() sea async, consideras:

public ConsultarTerceroDatosBasicosServiceResponse Consultar(string identificacion)
{
    return ConsultarAsync(identificacion).Result;
}

public async Task<ConsultarTerceroDatosBasicosServiceResponse> ConsultarAsync(string identificacion)
{
    HttpClient apiClient = await _factoryHttp.Crear(NamesEndPointConstants.DatosBasicos);
    HttpResponseMessage response = await apiClient.GetAsync($"api/Consulta/Tercero/{identificacion}");

    if (!response.IsSuccessStatusCode)
    {
        return new ConsultarTerceroDatosBasicosServiceResponse(null);
    }

    string respuesta = await response.Content.ReadAsStringAsync();
    var responseAgent = JsonConvert.DeserializeObject<TerceroModelView>(respuesta);
    return new ConsultarTerceroDatosBasicosServiceResponse(responseAgent);
}

Esto es más fácil de leer IMO.

Segundo, es posible que la request no está coincidiendo con el setup. Pruebas MockBehavior.Strict por favor... lanzaría un MockException con un mensaje de error más detallado si es así.

-private readonly Mock<HttpMessageHandler> _datosBasicosHandler = new();
+private readonly Mock<HttpMessageHandler> _datosBasicosHandler = new(MockBehavior.Strict);

MockBehavior.Loose (default):

Object reference not set to an instance of an object.

MockBehavior.Strict:

MockException ... RequestUri: 'https://api.github.com/not/mocked' ... All invocations on the mock must have a corresponding setup.

from moq.contrib.httpclient.

maxkagamine avatar maxkagamine commented on May 13, 2024

Por cierto, espero que no te importe, yo edité tu respuesta porque los code blocks fueron rotos. Para code de multi-línea, utilizas tres backticks con el nombre del idioma, como:

```cs
code
```

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Gracias por todo., desde Valledupar/Colombia.

Thanks for everything.
The issue was the Headers added by _factoryHttp.Create,

Congratulations on Moq.Contrib.HttpClient.

Maybe an example is missing mocking a request with a successful token.

            var urlRequest = $"https://signusfinanciero/datosbasicos/api/Consulta/Tercero/{terceroEntidad.Identificacion}";
            _datosBasicosHandler.SetupRequest(HttpMethod.Get, urlRequest, request =>
            {
                var token=request.Headers.Authorization.Scheme;
                var userName = request.Headers.GetValues("ByAUserName").FirstOrDefault();
                return token=="TOKEN" && userName=="boris";
            })
            .ReturnsResponse(JsonConvert.SerializeObject(terceroEntidad), "application/json");

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Funcionó bien pero otra vez error:

System.AggregateException : One or more errors occurred. (HttpMessageHandler.SendAsync(Method: GET, RequestUri: 'https://signusfinanciero/datosbasicos/api/Consulta/Tercero/1003195636', Version: 1.1, Content: , Headers:
{
Authorization: Bearer Token
ByAUserName: boris
}, CancellationToken) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.)
---- Moq.MockException : HttpMessageHandler.SendAsync(Method: GET, RequestUri: 'https://signusfinanciero/datosbasicos/api/Consulta/Tercero/1003195636', Version: 1.1, Content: , Headers:
{
Authorization: Bearer Token
ByAUserName: boris
}, CancellationToken) invocation failed with mock behavior Strict.
All invocations on the mock must have a corresponding setup.

from moq.contrib.httpclient.

maxkagamine avatar maxkagamine commented on May 13, 2024

Aquí, request.Headers.Authorization.Scheme es "Bearer" y .Parameter es "Token". ¿Puedes confirmar que el setup es correcto? Estoy viendo el ejemplo de arriba; es posible que estás comparando Scheme por accidente. (Y también "TOKEN" vs. "Token".)

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Ahora sí!!! Correcto. Green!

  
var urlRequest = $"https://signusfinanciero/datosbasicos/api/Consulta/Tercero/{terceroEntidad.Identificacion}";
            _datosBasicosHandler.SetupRequest(HttpMethod.Get, urlRequest, request =>
            {
                var scheme = request.Headers.Authorization.Scheme;
                var parameter = request.Headers.Authorization.Parameter;
                var userName = request.Headers.GetValues("ByAUserName").FirstOrDefault();
                return scheme == "Bearer" && parameter.ToUpper()=="TOKEN" && userName == "boris";
            })
            .ReturnsResponse(JsonConvert.SerializeObject(terceroEntidad), "application/json");

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Te comentó que me tocó suspender el Moq, no me funciona cuando ejecuto todas la pruebas juntas.
Escenario:
El test del que estamos tratando esta dentro de una clase abstracta. Esta clase abstracta la heredó a un clase de prueba para implementar una base de datos en memoria y otra clase prueba para implementar con SqlServer.
Tambien tengo varios tests en esas clases.

Solo ese método de prueba que estamos tratando es quien una el Mock a httpClient, por tanto se ejecuta para InMemory y para SqlServer.

Cuando ejecuto cada test individual funciona bien.
Cuando ejecuto solo los dos test del Mock (inMemory y SqlServer) funciona.
Cuando ejecuto esos dos test junto con otros test no funciona.
El error que presenta es como si los datos que verifica no coincidieran, pero yo depuro y son los correctos. Además observo como si se perdiera la configuración del Mock.

Es algo muy extraño. Por ahora suspendido el uso de tu Nuget ( parece muy bueno pero vimos esas limitantes que no comprendemos) y por ahora continuamos haciendo Mock al servicio que llamaba al HttpClient.

Espero que te sierva la retroalimentación y en el caso que nos puedas dar una sugerencias si es un problema de nosotros.

Gracias por todo el apoyo recibido.

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Este es el código de lo descrito anteriormente.

namespace Contabilidad.Infrastructure.Web.Test.ClasesDescuento
{
    public abstract class BaseClaseDescuentoControllerTest
    {
        protected string[] roles = new[] { RolesConstants.Integracion };
        protected BaseWebApplicationFactory<TestStartup> Factory { get; }
        protected ITestOutputHelper Output { get; }
        protected readonly Mock<HttpMessageHandler> _datosBasicosHandler = new(MockBehavior.Strict);
        protected BaseClaseDescuentoControllerTest(BaseWebApplicationFactory<TestStartup> factory, ITestOutputHelper output)
        {
            Output = output;
            Factory = factory;
            Factory.ConfigureTestServices(services =>
            {
                services.AddHttpClient(NamesEndPointConstants.DatosBasicos)
                    .ConfigurePrimaryHttpMessageHandler(() => _datosBasicosHandler.Object);

                //var mockConsultarTerceroDatosBasicosServiceHttp = Mock.Of<IConsultarTerceroDatosBasicosService>();
                //Mock.Get(mockConsultarTerceroDatosBasicosServiceHttp)
                //.Setup(m => m.Consultar(It.IsAny<string>()))
                //.Returns(new ConsultarTerceroDatosBasicosServiceResponse(new TerceroModelView() { Identificacion = "1003195636" }));

                //services.AddTransient<IConsultarTerceroDatosBasicosService>(sp => mockConsultarTerceroDatosBasicosServiceHttp);
            });

        }

        [Fact]
        public async Task GetClaseDescuentoTestAsync()
        {
            var httpClient = Factory.CreateClient().GenerarToken(roles);
            var vigencia = 2020;
            var result = await httpClient.GetAsync($"/api/ClaseDescuento/Vigencia/{vigencia}");
            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
        }

        [Fact]
        public async Task GetClaseDescuentoDatosFormularioTestAsync()
        {
            var httpClient = Factory.CreateClient().GenerarToken(roles);

            var vigencia = 2020;
            var result = await httpClient.GetAsync($"/api/ClaseDescuentoRegistro/DatosFormulario/{vigencia}");
            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
        }

        [Fact]
        public async Task PostClaseDescuentoRegistroTestAsync()
        {
            var httpClient = Factory.CreateClient().GenerarToken(roles);
            #region UsoEjemploMockPendiente
            TerceroModelView terceroEntidad = new() { Identificacion = "1003195636" };
            var urlRequest = $"https://signusfinanciero/datosbasicos/api/Consulta/Tercero/{terceroEntidad.Identificacion}";
            _datosBasicosHandler.SetupRequest(HttpMethod.Get, urlRequest, request =>
            {
                var schemeEnviado = request.Headers.Authorization.Scheme.ToLower();
                var parameterEnviado = request.Headers.Authorization.Parameter.ToLower();
                var userNameEnviado = request.Headers.GetValues("ByAUserName").FirstOrDefault();
                var validateResult =
                schemeEnviado == "bearer" &&
                parameterEnviado == "token" &&
                userNameEnviado == username;
                return validateResult;
            })
            .ReturnsResponse(JsonConvert.SerializeObject(terceroEntidad), "application/json");
            #endregion 

            //usar object Mother fluent
            var cuentaContableId = 1000;

            var request = new RegistrarClaseDescuentoRequest
            {
                Codigo = "123-121",
                ConNit = true,
                Nit = "1003195636",
                Municipal = true,
                IncluirEnAnticipo = true,
                Descripcion = "Clase de descuento Test",
                PorcentajeDescuento = 4,
                PorcentajeValorBase = 2,
                Redondeo = 4,
                CuentaContableId = cuentaContableId,
                Vigencia = 2020,
                CodigoTipoValorBase = "VSI"
            };
           
            var result = await httpClient.PostAsync("/api/ClaseDescuentoRegistro/", request);
            var jsonFromPostResponse = await result.Content.ReadAsStringAsync();
            Output.WriteLine(jsonFromPostResponse);
            Assert.Equal(HttpStatusCode.OK, result.StatusCode);

        }


    }
}


namespace Contabilidad.Infrastructure.Web.Test.InMemory.ClasesDescuento
{
    using Contabilidad.Infrastructure.Web.Test.ClasesDescuento;

    [Collection("DatabaseInMemory")]
    [Trait("Category", "Integration")]
    public class ClaseDescuentoControllerInMemoryTest :
       BaseClaseDescuentoControllerTest, IClassFixture<ApiWebApplicationInMemoryFactory>
    {
        public ClaseDescuentoControllerInMemoryTest
            (ApiWebApplicationInMemoryFactory factory, ITestOutputHelper output) : base(factory, output)
        {

        }
    }

}

namespace Contabilidad.Infrastructure.Web.Test.SqlServer.ClasesDescuento
{
    using Contabilidad.Infrastructure.Web.Test.ClasesDescuento;

    [Collection("DatabaseSqlServer")]
    [Trait("Category", "EndToEnd")]
    public class ClaseDescuentoControllerInSqlServerTest :
        BaseClaseDescuentoControllerTest, IClassFixture<ApiWebApplicationSqlServerFactory>
    {
        public ClaseDescuentoControllerInSqlServerTest(ApiWebApplicationSqlServerFactory factory, ITestOutputHelper output) : base(factory, output)
        {

        }
    }
}

namespace Contabilidad.Infrastructure.Web.Test.Postgresql.ClasesDescuento
{
    using Contabilidad.Infrastructure.Web.Test.ClasesDescuento;

    [Collection("DatabasePostgres")]
    [Trait("Category", "EndToEnd")]
    public class ClasesDescuentoControllerInPostgresqlTest :
        BaseClaseDescuentoControllerTest, IClassFixture<ApiWebApplicationPostgresFactory>
    {
        public ClasesDescuentoControllerInPostgresqlTest
            (ApiWebApplicationPostgresFactory factory, ITestOutputHelper output) : base(factory, output)
        {

        }
    }
}

from moq.contrib.httpclient.

borisgr04 avatar borisgr04 commented on May 13, 2024

Te dejo el código.
Gracias.

from moq.contrib.httpclient.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.