2016-11-29 3 views
37

Ich fühle mich, als würde ich etwas wirklich offensichtlich hier vermissen. Ich habe Klassen, die das Injizieren von Optionen erfordern, die das .Net Core IOptions-Muster (?) Verwenden. Wenn ich zum Komponententest gehe, möchte ich verschiedene Versionen der Optionen vortäuschen, um die Funktionalität der Klasse zu überprüfen. Kann jemand IOptionen außerhalb der Startup-Klasse korrekt nachahmen/instanziieren/auffüllen?.Net Core Unit Testing - Mock IOptionen <T>

Hier einige Beispiele der Klassen sind mit arbeite ich:

Einstellungen/Optionen Modell

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

Klasse getestet werden, welche die Einstellungen verwendet:

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-Test in einer anderen Baugruppe als die anderen Klassen:

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

Könnten Sie einen kleinen Codebeispiel des Blocks Sie zu verspotten versuchen? Vielen Dank! – axlj

+0

Verwechseln Sie die Bedeutung von Spott? Sie verspotten eine Schnittstelle und konfigurieren sie so, dass sie einen bestimmten Wert zurückgibt. Für 'IOptions ' müssen Sie nur 'Value' verspotten, um die Klasse zurückzugeben, die Sie wünschen – Tseng

Antwort

62

Sie müssen ein Objekt IOptions<SampleOptions> manuell erstellen und auffüllen. Dies können Sie über die Hilfsklasse Microsoft.Extensions.Options.Options tun. Zum Beispiel:

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

können Sie vereinfachen, dass ein bisschen zu:

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

Offensichtlich ist dies nicht sehr nützlich ist, wie es ist. Sie müssen ein SampleOptions-Objekt tatsächlich erstellen und auffüllen und dieses an die Create-Methode übergeben.

18

Wenn Sie beabsichtigen, das Mocking-Framework gemäß @TSeng im Kommentar zu verwenden, müssen Sie die folgende Abhängigkeit in Ihre project.json-Datei einfügen.

"Moq": "4.6.38-alpha", 

Sobald die Abhängigkeit wieder hergestellt wird, ist das MOQ Framework so einfach wie eine Instanz der Klasse SampleOptions erstellen und dann über den Wert zuweisen erwähnt.

Hier ist ein Code umreißen, wie es aussehen würde.

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

Sobald das Mock-Setup ist, können Sie nun das Mock-Objekt zum contructor als

SampleRepo sr = new SampleRepo(mock.Object); 

HTH passieren.

FYI habe ich ein Git Repository, das diese zwei Ansätze auf Github/patvin80

4

Sie umreißt überhaupt mit MOQ vermeiden. Verwenden Sie in Ihrer Tests .json Konfigurationsdatei. Eine Datei für viele Testklassendateien. Es wird in Ordnung sein, ConfigurationBuilder in diesem Fall zu verwenden.

Beispiel für Appsetting.json

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

Beispiel für Einstellungen Mapping Klasse:

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

Bespiel für Service, der Test benötigt wird:

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

NUnit Testklasse:

[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

Das funktionierte gut für mich, Prost! Ich wollte Moq nicht für etwas verwenden, das so einfach zu sein schien und wollte nicht versuchen, meine eigenen Optionen mit Konfigurationseinstellungen zu füllen. – Harry

0

Für meine System- und Integrationstests Ich bevorzuge eine Kopie/einen Link meiner Konfigurationsdatei innerhalb des Testprojekts. Und dann verwende ich den ConfigurationBuilder, um die Optionen zu erhalten.

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

So kann ich die Konfiguration überall in meinem TestProject verwenden. Für Komponententests bevorzuge ich MOQ wie patvin80 beschrieben.

0

Hier ist eine weitere einfache Möglichkeit, die Mock nicht braucht, sondern verwendet stattdessen die 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);