Pruebas de unidad central de .NET: IOptions simuladas<T>

Resuelto Matt asked hace 8 años • 0 respuestas

Siento que me estoy perdiendo algo realmente obvio aquí. Tengo clases que requieren la inyección de opciones usando el IOptionspatrón .NET Core (?). Cuando hago una prueba unitaria de esa clase, quiero simular varias versiones de las opciones para validar la funcionalidad de la clase. ¿Alguien sabe cómo simular/crear instancias/rellenar correctamente IOptions<T>fuera de la clase de Inicio?

Aquí hay algunos ejemplos de las clases con las que estoy trabajando:

Modelo de configuración/opciones

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace OptionsSample.Models
{
    public class SampleOptions
    {
        public string FirstSetting { get; set; }
        public int SecondSetting { get; set; }
    }
}

Clase a probar que utiliza la Configuración:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OptionsSample.Models
using System.Net.Http;
using Microsoft.Extensions.Options;
using System.IO;
using Microsoft.AspNetCore.Http;
using System.Xml.Linq;
using Newtonsoft.Json;
using System.Dynamic;
using Microsoft.Extensions.Logging;

namespace OptionsSample.Repositories
{
    public class SampleRepo : ISampleRepo
    {
        private SampleOptions _options;
        private ILogger<AzureStorageQueuePassthru> _logger;

        public SampleRepo(IOptions<SampleOptions> options)
        {
            _options = options.Value;
        }

        public async Task Get()
        {
        }
    }
}

Prueba unitaria en un ensamblado diferente al de las otras clases:

using OptionsSample.Repositories;
using OptionsSample.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

namespace OptionsSample.Repositories.Tests
{
    public class SampleRepoTests
    {
        private IOptions<SampleOptions> _options;
        private SampleRepo _sampleRepo;


        public SampleRepoTests()
        {
            //Not sure how to populate IOptions<SampleOptions> here
            _options = options;

            _sampleRepo = new SampleRepo(_options);
        }
    }
}
Matt avatar Nov 30 '16 04:11 Matt
Aceptado

Debe crear y completar manualmente un IOptions<SampleOptions>objeto. Puede hacerlo a través de la Microsoft.Extensions.Options.Optionsclase de ayuda. Por ejemplo:

IOptions<SampleOptions> someOptions = Options.Create<SampleOptions>(new SampleOptions());

Puedes simplificarlo un poco para:

var someOptions = Options.Create(new SampleOptions());

Obviamente esto no es muy útil tal como está. Necesitará crear y completar un objeto SampleOptions y pasarlo al método Create.

Necoras avatar Nov 30 '2016 21:11 Necoras

Si tiene la intención de utilizar Mocking Framework como lo indica @TSeng en el comentario, debe agregar la siguiente dependencia en su archivo project.json.

   "Moq": "4.6.38-alpha",

Una vez que se restablece la dependencia, usar el marco MOQ es tan simple como crear una instancia de la clase SampleOptions y luego, como se mencionó, asignarla al Valor.

Aquí hay un resumen del código de cómo se vería.

SampleOptions app = new SampleOptions(){Title="New Website Title Mocked"}; // Sample property
// Make sure you include using Moq;
var mock = new Mock<IOptions<SampleOptions>>();
// We need to set the Value of IOptions to be the SampleOptions Class
mock.Setup(ap => ap.Value).Returns(app);

Una vez configurado el simulacro, ahora puede pasar el objeto simulado al constructor como

SampleRepo sr = new SampleRepo(mock.Object);   

HTH.

Para su información, tengo un repositorio de Git que describe estos dos enfoques en Github/patvin80.

patvin80 avatar Jan 21 '2017 18:01 patvin80

Puede evitar el uso de MOQ en absoluto. Utilice en sus pruebas el archivo de configuración .json. Un archivo para muchos archivos de clases de prueba. Estará bien usarlo ConfigurationBuilderen este caso.

Ejemplo de appsetting.json

{
    "someService" {
        "someProp": "someValue
    }
}

Ejemplo de clase de mapeo de configuración:

public class SomeServiceConfiguration
{
     public string SomeProp { get; set; }
}

Ejemplo de servicio que se necesita para probar:

public class SomeService
{
    public SomeService(IOptions<SomeServiceConfiguration> config)
    {
        _config = config ?? throw new ArgumentNullException(nameof(_config));
    }
}

Clase de prueba NUnit:

[TestFixture]
public class SomeServiceTests
{

    private IOptions<SomeServiceConfiguration> _config;
    private SomeService _service;

    [OneTimeSetUp]
    public void GlobalPrepare()
    {
         var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", false)
            .Build();

        _config = Options.Create(configuration.GetSection("someService").Get<SomeServiceConfiguration>());
    }

    [SetUp]
    public void PerTestPrepare()
    {
        _service = new SomeService(_config);
    }
}
aleha_84 avatar Nov 15 '2017 10:11 aleha_84

Siempre puedes crear tus opciones a través de Options.Create() y luego simplemente usar AutoMocker.Use(options) antes de crear la instancia simulada del repositorio que estás probando. El uso de AutoMocker.CreateInstance<>() facilita la creación de instancias sin pasar parámetros manualmente

Cambié un poco tu SampleRepo para poder reproducir el comportamiento que creo que quieres lograr.

public class SampleRepoTests
{
    private readonly AutoMocker _mocker = new AutoMocker();
    private readonly ISampleRepo _sampleRepo;

    private readonly IOptions<SampleOptions> _options = Options.Create(new SampleOptions()
        {FirstSetting = "firstSetting"});

    public SampleRepoTests()
    {
        _mocker.Use(_options);
        _sampleRepo = _mocker.CreateInstance<SampleRepo>();
    }

    [Fact]
    public void Test_Options_Injected()
    {
        var firstSetting = _sampleRepo.GetFirstSetting();
        Assert.True(firstSetting == "firstSetting");
    }
}

public class SampleRepo : ISampleRepo
{
    private SampleOptions _options;

    public SampleRepo(IOptions<SampleOptions> options)
    {
        _options = options.Value;
    }

    public string GetFirstSetting()
    {
        return _options.FirstSetting;
    }
}

public interface ISampleRepo
{
    string GetFirstSetting();
}

public class SampleOptions
{
    public string FirstSetting { get; set; }
}
matei avatar Jun 27 '2019 15:06 matei