2016-11-29 21 views
37

Czuję, że brakuje tu czegoś naprawdę oczywistego. Mam klasy, które wymagają wstrzykiwania opcji przy użyciu wzorca .Net Core IOptions (?). Kiedy przechodzę do testu jednostkowego tej klasy, chcę kpić z różnych wersji opcji sprawdzania poprawności funkcjonalności klasy. Czy ktoś wie, jak poprawnie symulować/tworzenie instancji/wypełnianie IOptions poza klasy Startup?. Testowanie jednostki głównej .Net - makiety IOptions <T>

Oto kilka próbek klas pracuję z:

Ustawienia/Opcje modelu

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

Klasa być badane, które korzysta z ustawień: test

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

Jednostka w innym zestawie z innych klas:

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

można udostępnić mały przykład kodu bloku próbujesz drwić? dzięki! – axlj

+0

Czy mylisz znaczenie kpiny? Kpisz z interfejsu i konfigurujesz go, aby zwracał określoną wartość. Dla 'IOptions ' musisz tylko sfałszować 'Value', aby zwrócić klasę, której pragniesz – Tseng

Odpowiedz

62

Należy ręcznie utworzyć i wypełnić obiekt IOptions<SampleOptions>. Możesz to zrobić za pomocą klasy pomocniczej Microsoft.Extensions.Options.Options. Na przykład:

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

można uprościć, że nieco się:

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

Oczywiście nie jest to bardzo przydatne, jak jest. Będziesz musiał utworzyć i wypełnić obiekt SampleOptions i przekazać go do metody Create.

18

Jeśli masz zamiar użyć szyderczego frameworka wskazanego przez @TSeng w komentarzu, musisz dodać następującą zależność do pliku project.json.

"Moq": "4.6.38-alpha", 

Gdy zależność jest przywrócone, przy użyciu ramy MOQ jest tak proste jak tworzenie instancji klasy SampleOptions a następnie jak wspomniano przypisać go do wartości.

Oto zarys kodu, jak by wyglądał.

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

Po makiety jest ustawiony, można teraz przejść atrapa obiektu do contructor jak

SampleRepo sr = new SampleRepo(mock.Object); 

HTH.

FYI Mam repozytorium git, który nakreśla te 2 podejścia do Github/patvin80

4

można uniknąć stosując MOQ w ogóle. Użyj w pliku konfiguracyjnym testów .json. Jeden plik dla wielu plików testowych. W takim przypadku dobrze będzie użyć ConfigurationBuilder.

Przykład składania.json

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

Przykład klasy ustawień mapowania:

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

Przykład usługi, która jest potrzebna do testu:

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

NUnit klasa Test:

[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

To działało ładnie dla mnie, na zdrowie! Nie chciałem używać Moq do czegoś, co wydawało się takie proste i nie chciałem wypróbowywać własnych opcji z ustawieniami konfiguracyjnymi. – Harry

0

dla mojego testy systemowe i integracyjne Wolę mieć kopię/link mojego pliku konfiguracyjnego wewnątrz projektu testowego. A potem używam ConfigurationBuilder, aby uzyskać opcje.

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

W ten sposób mogę użyć config wszędzie wewnątrz mojego TestProject. Dla testów jednostkowych wolę używać MOQ jak opisywanego patvin80.

0

Oto kolejny prosty sposób, który nie wymaga Mock, ale zamiast tego używa 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);