2016-11-29 1 views
37

Я чувствую, что мне не хватает чего-то действительно очевидного здесь. У меня есть классы, которые требуют инъекции опций, используя шаблон .Net Core IOptions (?). Когда я перехожу к модулю тестирования этого класса, я хочу издеваться над различными версиями опций для проверки функциональности класса. Кто-нибудь знает, как правильно издеваться/создавать/заполнять IOptions за пределами класса Startup?. Чистое тестирование базового блока - Mock IOptions <T>

Вот некоторые образцы классов, я работаю с:

Настройки/Параметры модели

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; } 
    } 
} 

Класс для тестирования, который использует параметры: тест

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() 
     { 
     } 
    } 
} 

Unit в другой сборке из других классов:

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); 
     } 
    } 
} 
+1

могли бы вы предоставить небольшой пример кода блока, который вы пытаетесь дразнить? благодаря! – axlj

+0

Вы смешиваете смысл насмешек? Вы издеваетесь над интерфейсом и настраиваете его для возврата указанного значения. Для 'IOptions ' вам нужно только высмеять 'Value', чтобы вернуть класс, который вы хотите – Tseng

ответ

62

Вам необходимо вручную создать и заполнить объект IOptions<SampleOptions>. Вы можете сделать это с помощью вспомогательного класса Microsoft.Extensions.Options.Options. Например:

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

Вы можете упростить, что бит:

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

Очевидно, что это не очень полезно, как есть. Вам нужно будет фактически создать и заполнить объект SampleOptions и передать его в метод Create.

18

Если вы хотите использовать Mocking Framework, как указано в комментарии @TSeng в комментарии, вам нужно добавить следующую зависимость в файл project.json.

"Moq": "4.6.38-alpha", 

После зависимость восстанавливается, используя рамки MOQ так просто, как создание экземпляра класса SampleOptions, а затем, как указано присвоить ему значение.

Вот код, как он будет выглядеть.

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); 

После того, как макет настроен, теперь вы можете передать фиктивный объект в качестве застройщик

SampleRepo sr = new SampleRepo(mock.Object); 

HTH.

FYI У меня есть репозиторий, который обрисовывает в общих чертах эти 2 подхода по Github/patvin80

4

Вы можете избежать использования MOQ на всех. Используйте в своем файле конфигурации .json. Один файл для многих файлов тестовых классов. В этом случае будет нормально использовать ConfigurationBuilder.

Примеры применения.JSON

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

Пример класса настройки отображения:

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

Пример сервиса, который необходим для теста:

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

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); 
    } 
} 
+0

Это сработало для меня, ура! Не хотел использовать Moq для чего-то, что казалось таким простым и не хотело пытаться заполнить мои собственные параметры настройками конфигурации. – Harry

0

Для моего системные и интеграционные тесты Я предпочитаю иметь копию/ссылку моего файла конфигурации внутри тестового проекта. И затем я использую ConfigurationBuilder для получения параметров.

using System.Linq; 
using Microsoft.Extensions.Configuration; 
using Microsoft.Extensions.DependencyInjection; 

namespace SomeProject.Test 
{ 
public static class TestEnvironment 
{ 
    private static object configLock = new object(); 

    public static ServiceProvider ServiceProvider { get; private set; } 
    public static T GetOption<T>() 
    { 
     lock (configLock) 
     { 
      if (ServiceProvider != null) return (T)ServiceProvider.GetServices(typeof(T)).First(); 

      var builder = new ConfigurationBuilder() 
       .AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true) 
       .AddEnvironmentVariables(); 
      var configuration = builder.Build(); 
      var services = new ServiceCollection(); 
      services.AddOptions(); 

      services.Configure<ProductOptions>(configuration.GetSection("Products")); 
      services.Configure<MonitoringOptions>(configuration.GetSection("Monitoring")); 
      services.Configure<WcfServiceOptions>(configuration.GetSection("Services")); 
      ServiceProvider = services.BuildServiceProvider(); 
      return (T)ServiceProvider.GetServices(typeof(T)).First(); 
     } 
    } 
} 
} 

Таким образом, я могу использовать конфигурацию везде внутри моего тестового проекта. Для модульных тестов я предпочитаю использовать MOQ, как описано patvin80.

0

Вот еще один простой способ, который не нуждается в Мок, но вместо этого использует OptionsWrapper:

var myAppSettingsOptions = new MyAppSettingsOptions(); 
appSettingsOptions.MyObjects = new MyObject[]{new MyObject(){MyProp1 = "one", MyProp2 = "two", }}; 
var optionsWrapper = new OptionsWrapper<MyAppSettingsOptions>(myAppSettingsOptions); 
var myClassToTest = new MyClassToTest(optionsWrapper); 
Смежные вопросы