Lesen Sie die JSON-Werte für Apps in .NET Core Test Project

74

Meine Webanwendung muss die Dokument-DB-Schlüssel aus der Datei appsettings.json lesen. Ich habe eine Klasse mit den Schlüsselnamen erstellt und den Abschnitt Config ConfigureaServices()wie folgt gelesen :

public Startup(IHostingEnvironment env) {
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddEnvironmentVariables();

    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services) {
    services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
    services.AddSession();
    Helpers.GetConfigurationSettings(services, Configuration);
    DIBuilder.AddDependency(services, Configuration);
}

Ich suche nach Möglichkeiten, die Schlüsselwerte im Testprojekt zu lesen.

S.Siva
quelle

Antworten:

102

Dies basiert auf dem Blogbeitrag Verwenden von Konfigurationsdateien in .NET Core-Komponententestprojekten (geschrieben für .NET Core 1.0).

  1. Erstellen (oder kopieren) Sie die Datei appsettings.test.json im Stammverzeichnis des Integrationstestprojekts und geben Sie in den Eigenschaften "Build Action" als Inhalt und "Copy if new" in das Ausgabeverzeichnis an. Beachten Sie, dass es besser ist, einen anderen Dateinamen (z. B. appsettings.test.json) als normal zu verwenden appsettings.json, da möglicherweise eine Datei aus dem Hauptprojekt die Datei aus dem Testprojekt überschreibt, wenn derselbe Name verwendet wird.

  2. Fügen Sie das NuGet-Paket der JSON-Konfigurationsdatei (Microsoft.Extensions.Configuration.Json) hinzu, falls es noch nicht enthalten ist.

  3. Erstellen Sie im Testprojekt eine Methode,

    public static IConfiguration InitConfiguration()
            {
                var config = new ConfigurationBuilder()
                    .AddJsonFile("appsettings.test.json")
                    .Build();
                    return config;
            }
    
  4. Verwenden Sie die Konfiguration wie gewohnt

    var config = InitConfiguration();
    var clientId = config["CLIENT_ID"]
    

Übrigens: Möglicherweise ist es auch interessant, die Konfiguration in die IOptions-Klasse einzulesen, wie im Integrationstest mit IOptions <> in .NET Core beschrieben :

var options = config.Get<MySettings>();
Michael Freidgeim
quelle
1
config.Get <MySettings> () gibt einen leeren Wert zurück. Sie sollten IOptions wie diese verwenden. stackoverflow.com/questions/46019988/...
kaya
Vielen Dank
Sunil Dhappadhule
13

Fügen Sie die Konfigurationsdatei hinzu

Fügen Sie zunächst dem Integrationstestprojekt eine Datei appconfig.json hinzu

Konfigurieren Sie die Datei appconfig.json, die durch Aktualisierung in das Ausgabeverzeichnis kopiert werden soll

Geben Sie hier die Bildbeschreibung ein

NuGet-Paket hinzufügen

  • Microsoft.Extensions.Configuration.Json

Verwenden Sie die Konfiguration in Ihren Unit-Tests

[TestClass]
public class IntegrationTests
{
    public IntegrationTests()
    {
        var config = new ConfigurationBuilder().AddJsonFile("appconfig.json").Build();
        
        _numberOfPumps = Convert.ToInt32(config["NumberOfPumps"]);

        _numberOfMessages = Convert.ToInt32(config["NumberOfMessages"]);

        _databaseUrl = config["DatabaseUrlAddress"];
    }
} 
Amir Touitou
quelle
10

Die Lösung von Suderson hat bei mir wie folgt funktioniert:

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddEnvironmentVariables();

    IConfiguration config = builder.Build();

    //Now, You can use config.GetSection(key) to get the config entries
Bob Ash
quelle
3

Kopieren Sie das appSettings.jsonin das Stammverzeichnis Ihres Testprojekts und markieren Sie dessen Eigenschaft als Inhalt und Kopieren, falls neu .

var builder = new ConfigurationBuilder()
  .SetBasePath(Directory.GetCurrentDirectory())
  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  .AddEnvironmentVariables();
ConfigurationManager.Configuration = builder.Build();

ConfigurationManagerist eine Klasse und hat eine statische Eigenschaft Configuration. Auf diese Weise kann die gesamte Anwendung einfach als darauf zugreifenConfigurationManager.Configuration[<key>]

Sudherson Vetrichelvan
quelle
3
Die erste Hälfte ist richtig. Verwenden des statischen ConfigurationManager.Configuration klingt nicht korrekt.
Michael Freidgeim
2

Fügen project.jsonSie im Testprojekt von Ihnen die folgenden Abhängigkeiten hinzu:

"dependencies": {
  "xunit": "2.2.0-beta2-build3300",
  "Microsoft.AspNetCore.TestHost": "1.0.0",
  "dotnet-test-xunit": "2.2.0-preview2-build1029",
  "BancoSentencas": "1.0.0-*"
},

BancoSentencasist das Projekt, das ich testen möchte. Die anderen Pakete stammen von xUnit und dem TestHost, der unser In-Memory-Server sein wird.

Fügen Sie auch diese Build-Option für die Datei appsettings.json hinzu:

"buildOptions": {
  "copyToOutput": {
    "include": [ "appsettings.Development.json" ]
  }
}

In meinem Testprojekt habe ich folgende Testklasse:

  public class ClasseControllerTeste : IClassFixture<TestServerFixture> {

    public ClasseControllerTeste(TestServerFixture fixture) {
      Fixture = fixture;
    }

    protected TestServerFixture Fixture { get; private set; }


    [Fact]
    public async void TestarRecuperarClassePorId() {
      using(var client = Fixture.Client) {
        var request = await Fixture.MyHttpRequestMessage(HttpMethod.Get, "/api/classe/1436");
        var response = await client.SendAsync(request);
        string obj = await response.Content.ReadAsStringAsync();
        ClasseModel classe = JsonConvert.DeserializeObject<ClasseModel>(obj);
        Assert.NotNull(classe);
        Assert.Equal(1436, classe.Id);
      }
    }
  }

Außerdem habe ich die TestServerFixture-Klasse, mit der der In-Memory-Server konfiguriert wird:

  public class TestServerFixture : IDisposable {
    private TestServer testServer;
    protected TestServer TestServer {
      get {
        if (testServer == null)
          testServer = new TestServer(new WebHostBuilder().UseEnvironment("Development").UseStartup<Startup>());
        return testServer;
      }
    }

    protected SetCookieHeaderValue Cookie { get; set; }

    public HttpClient Client {
      get {
        return TestServer.CreateClient();
      }
    }

    public async Task<HttpRequestMessage> MyHttpRequestMessage(HttpMethod method, string requestUri) {      
      ...
      login stuff...
      ...
      Cookie = SetCookieHeaderValue.Parse(response.Headers.GetValues("Set-Cookie").First());

      var request = new HttpRequestMessage(method, requestUri);

      request.Headers.Add("Cookie", new CookieHeaderValue(Cookie.Name, Cookie.Value).ToString());
      request.Headers.Accept.ParseAdd("text/xml");
      request.Headers.AcceptCharset.ParseAdd("utf-8");
      return request;
    }

    public void Dispose() {
      if (testServer != null) {
        testServer.Dispose();
        testServer = null;
      }
    }
  }

So teste ich mein Projekt. Ich verwende die Startup.cs aus dem Hauptprojekt und erstelle in meinem Testprojekt (appsettings.Development.json) eine Kopie aus appsettings.json.

Fabricio Koch
quelle
Was ist dieser TestServer? Deine benutzerdefinierte Klasse?
S.Siva
Es ist eine Klasse aus Microsoft.AspNetCore.TestHostPaket. Verwenden Sie xUnit? Ich werde meine Antwort bearbeiten und weitere Details bereitstellen.
Fabricio Koch
Ja. Ich benutze auch xUnit.
S.Siva
Danke für den detaillierten Code. Meine Anwendung ist keine Web-API. Also hilf mir, wie kann ich es testen?
S.Siva
Ihre App ist also eine MVC, oder? Möchten Sie Ihren MVC-Controller testen?
Fabricio Koch
1

Ich lese die Konfiguration lieber aus einem Stream als aus einer Datei. Dies bietet mehr Flexibilität, da Sie ein leichtes Test-Setup erstellen können, ohne mehrere JSON-Konfigurationsdateien festzuschreiben:

public static class ConfigurationHelper
{
    public static IConfigurationRoot GetConfiguration()
    {
        byte[] byteArray = Encoding.ASCII.GetBytes("{\"Root\":{\"Section\": { ... }}");
        using var stream = new MemoryStream(byteArray);
        return new ConfigurationBuilder()
            .AddJsonStream(stream)
            .Build();
    }
}
Artem
quelle
1

Ähnlich wie bei Artem , jedoch mit einer eingebetteten Ressource (als Stream):

Stream configStream =
    Assembly.GetExecutingAssembly()
    .GetManifestResourceStream("MyNamespace.AppName.Test.appsettings.test.json");

IConfigurationRoot config = new ConfigurationBuilder()
    .AddJsonStream(configStream)
    .AddEnvironmentVariables()
    .Build();

Geben Sie hier die Bildbeschreibung ein

Adam Cox
quelle
0

Wenn Sie eine Anwendung auf Einheit testen , sollten Sie ehrlich gesagt versuchen, die zu testende Klasse von allen Abhängigkeiten zu isolieren, z. B. vom Aufrufen anderer Klassen, dem Zugriff auf Dateisystem, Datenbank, Netzwerk usw., es sei denn, Sie führen Integrationstests oder Funktionstests durch.

Um die Anwendung zu testen, möchten Sie diese Werte wahrscheinlich aus Ihrer Datei appsettings.json verspotten und einfach Ihre Logik testen.

Du appsettings.jsonwürdest also so aussehen.

"DocumentDb": {
    "Key": "key1" 
} 

Erstellen Sie dann eine Einstellungsklasse.

public class DocumentDbSettings
{
    public string Key { get; set; }
}

Dann registrieren Sie es in ConfigureServices()Methode.

services.Configure<DocumentDbSettings>(Configuration.GetSection("DocumentDb"));

Dann könnte beispielsweise Ihr Controller / Ihre Klasse so aussehen.

// ...
private readonly DocumentDbSettings _settings;

public HomeController(IOptions<DocumentDbSettings> settings)
{
    _settings = settings.Value;
}
// ...
public string TestMe()
{
    return $"processed_{_settings.Key}";
}

Dann können Sie in Ihrem Testprojekt eine solche Unit-Test-Klasse erstellen.

public class HomeControllerTests
{
    [Fact]
    public void TestMe_KeyShouldBeEqual_WhenKeyIsKey1()
    {
        // Arrange
        const string expectedValue = "processed_key1";
        var configMock = Substitute.For<IOptions<DocumentDbSettings>>();
        configMock.Value.Returns(new DocumentDbSettings
        {
            Key = "key1" // Mocking the value from your config
        });

        var c = new HomeController(configMock);

        // Act
        var result = c.TestMe();

        // Assert
        Assert.Equal(expectedValue, result);
    }
}

Ich habe NSubstitute v2.0.0-rc zum Verspotten verwendet.

Ignas
quelle
25
Ja, aber ... was ist, wenn ich Integrationstests mache? Sie haben die eigentliche Frage nicht beantwortet
Joe Phillips
0

Kopieren Sie bei ASP.NET Core 2.x-Projekten die appsettings.jsonDatei automatisch in das Build-Verzeichnis:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <None Include="..\MyProj\appsettings.json" CopyToOutputDirectory="PreserveNewest" />
  </ItemGroup>
</Project>
VahidN
quelle
Dies funktioniert und VS ist klug genug zu wissen, dass es sich um dieselbe Datei handelt. Natürlich werden alle Änderungen, die Sie an Ihrer Testversion vornehmen, in die Serverversion repliziert, da es sich um dieselbe Datei handelt.
keithl8041
0

Wenn Sie WebApplicationFactory einen Testserver für Integrationstests erstellen und bereits die Möglichkeit haben, Konfigurationswerte in Ihren serverseitigen Controllern abzurufen (wahrscheinlich auch!), Können Sie diesen einfach wiederverwenden (und auf einen anderen zugreifen) injizierte Elemente, die Sie benötigen) in Ihren Integrationstests wie folgt:

// Your test fixtures would be subclasses of this
public class IntegrationTestBase : IDisposable
{
    private readonly WebApplicationFactory<Startup> _factory;
    protected readonly HttpClient _client;

    // The same config class which would be injected into your server-side controllers
    protected readonly IMyConfigService _myConfigService;

    // Constructor (called by subclasses)
    protected IntegrationTestBase()
    {
        // this can refer to the actual live Startup class!
        _factory = new WebApplicationFactory<Startup>();
        _client = _factory.CreateClient();

        // fetch some useful objects from the injection service
        _myConfigService = (IMyConfigService)_factory.Server.Host.Services.GetService(typeof(IMyConfigService));
    }

    public virtual void Dispose()
    {
        _client.Dispose();
        _factory.Dispose();
    }
}

Beachten Sie, dass Sie appsettings.jsonin diesem Fall nicht kopieren müssen. Sie verwenden automatisch dasselbe, appsettings.jsondas der (Test-) Server verwendet.

MikeBeaton
quelle
Hallo Mike, ich verwende dieselbe Methode, die Sie vorgeschlagen haben. Aber ich muss einige Einstellungen überschreiben, ich konnte keinen Weg finden, das zu machen. Irgendwelche Vorschläge?
fkucuk
Hallo, das macht Sinn. Ich brauche nur, dass meine Einstellungen für den Integrationstest mit meinen Entwicklungseinstellungen übereinstimmen. Ich denke appsettings.json, unterstützt nur Entwicklung, Produktion und Staging. Wenn Sie also eine vierte Variante für Test benötigen, bin ich mir nicht sicher. Ich vermute, dass es eine Möglichkeit geben würde, eine zusätzliche Konfiguration einzufügen (da ich denke, dass alle Konfigurationen der Reihe nach durchsucht werden), die überschreiben würde, was Sie benötigen.
MikeBeaton