it-swarm.com.de

So testen Sie Unit Startup.cs in .NET Core

Wie gehen die Leute beim Unit Testing ihrer Startup.cs-Klassen in einer .NET Core 2-Anwendung vor? Die gesamte Funktionalität scheint durch statische Erweiterungsmethoden bereitgestellt zu werden, die nicht zu verspotten sind.

Wenn Sie zum Beispiel diese ConfigureServices-Methode verwenden:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<BlogContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc();
}

Wie schreibe ich Tests, um sicherzustellen, dass AddDbContext (...) & AddMvc () aufgerufen wird. Die Entscheidung, all diese Funktionalität über Extensions-Methoden zu implementieren, scheint sie untestbar zu machen.

15
Rob Earlam

Nun ja, wenn Sie die Tatsache prüfen wollen, dass die Erweiterungsmethode AddDbContext für services aufgerufen wurde, haben Sie Probleme. Das Gute ist, dass Sie nicht genau diese Tatsache überprüfen sollten.

Startup class ist eine Anwendung Zusammensetzung root . Wenn Sie einen Kompositionsstamm testen, möchten Sie prüfen, ob er tatsächlich alle Abhängigkeiten registriert, die für die Instantiierung der Stammobjekte (Controller im Fall der ASP.NET Core-Anwendung) erforderlich sind.

Angenommen, Sie haben folgenden Controller:

public class TestController : Controller
{
    public TestController(ISomeDependency dependency)
    {
    }
}

Sie könnten versuchen zu prüfen, ob Startup den Typ für ISomeDependency registriert hat. Die Implementierung von ISomeDependency könnte jedoch auch einige andere Abhängigkeiten erfordern, die Sie überprüfen sollten . Am Ende erhalten Sie einen Test, der unzählige Überprüfungen für verschiedene Abhängigkeiten enthält, jedoch nicht garantiert, dass die Objektauflösung keine fehlende Abhängigkeitsausnahme auslöst. Bei einem solchen Test ist der Wert nicht zu groß.

Ein Ansatz, der für mich beim Testen einer Kompositionswurzel gut funktioniert, ist die Verwendung eines realen Abhängigkeitsinjektionscontainers. Dann nenne ich eine Kompositionswurzel und versichere, dass die Auflösung des Wurzelobjekts nicht ausgelöst wird.

Es kann nicht als reiner Komponententest betrachtet werden, da wir andere Klassen ohne Stummel verwenden. Solche Tests sind jedoch im Gegensatz zu anderen Integrationstests schnell und stabil. Und am wichtigsten ist, dass sie den Wert einer gültigen Überprüfung auf korrekte Abhängigkeiten erheben. Wenn ein solcher Test erfolgreich ist, können Sie sicher sein, dass das Objekt auch im Produkt korrekt instanziiert wird.

Hier ist ein Beispiel eines solchen Tests:

[TestMethod]
public void ConfigureServices_RegistersDependenciesCorrectly()
{
    //  Arrange

    //  Setting up the stuff required for Configuration.GetConnectionString("DefaultConnection")
    Mock<IConfigurationSection> configurationSectionStub = new Mock<IConfigurationSection>();
    configurationSectionStub.Setup(x => x["DefaultConnection"]).Returns("TestConnectionString");
    Mock<Microsoft.Extensions.Configuration.IConfiguration> configurationStub = new Mock<Microsoft.Extensions.Configuration.IConfiguration>();
    configurationStub.Setup(x => x.GetSection("ConnectionStrings")).Returns(configurationSectionStub.Object);

    IServiceCollection services = new ServiceCollection();
    var target = new Startup(configurationStub.Object);

    //  Act

    target.ConfigureServices(services);
    //  Mimic internal asp.net core logic.
    services.AddTransient<TestController>();

    //  Assert

    var serviceProvider = services.BuildServiceProvider();

    var controller = serviceProvider.GetService<TestController>();
    Assert.IsNotNull(controller);
}
18
CodeFuller

Dieser Ansatz funktioniert und verwendet die echte MVC-Pipeline, da Dinge nur verspottet werden sollten, wenn Sie ändern müssen, wie sie funktionieren. 

    public void AddTransactionLoggingCreatesConnection()
    {

        var servCollection = new ServiceCollection();

        //Add any injection stuff you need here
        //servCollection.AddSingleton(logger.Object);

        //Setup the MVC builder thats needed
        IMvcBuilder mvcBuilder = new MvcBuilder(servCollection, new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager());

        IEnumerable<KeyValuePair<string, string>> confValues = new List<KeyValuePair<string, string>>()
        {
            new KeyValuePair<string, string>("TransactionLogging:Enabled", "True"),
            new KeyValuePair<string, string>("TransactionLogging:Uri", "https://api.something.com/"),
            new KeyValuePair<string, string>("TransactionLogging:Version", "1"),
            new KeyValuePair<string, string>("TransactionLogging:Queue:Enabled", "True"),

        ConfigurationBuilder builder = new ConfigurationBuilder();
        builder.AddInMemoryCollection(confValues);

        var confRoot = builder.Build();

        StartupExtensions.YourExtensionMethod(mvcBuilder // Any other params);
    }
}
0
Stuart.Sklinar

Ich hatte auch ein ähnliches Problem, aber es gelang mir, dieses Problem zu umgehen, indem ich den WebHost in AspNetCore verwendete und im Wesentlichen neu erstellte, was program.cs tut, und dann zu behaupten, dass alle meine Dienste vorhanden und nicht null sind. Sie können einen Schritt weiter gehen und bestimmte Erweiterungen für IServices mit .ConfigureServices ausführen oder Operationen mit den von Ihnen erstellten Diensten ausführen, um sicherzustellen, dass diese ordnungsgemäß erstellt wurden.

Ein Schlüssel ist, dass ich eine Unit-Test-Startklasse erstellt habe, die von der Startklasse erbt, die ich teste, damit ich mich nicht um separate Assemblys kümmern muss. Sie können Komposition verwenden, wenn Sie keine Vererbung verwenden möchten.

[TestClass]
public class StartupTests
{
    [TestMethod]
    public void StartupTest()
    {
        var webHost = Microsoft.AspNetCore.WebHost.CreateDefaultBuilder().UseStartup<Startup>().Build();
        Assert.IsNotNull(webHost);
        Assert.IsNotNull(webHost.Services.GetRequiredService<IService1>());
        Assert.IsNotNull(webHost.Services.GetRequiredService<IService2>());
    }
}

public class Startup : MyStartup
{
    public Startup(IConfiguration config) : base(config) { }
}
0
War Gravy