web-dev-qa-db-fra.com

Comment effectuer des tests unitaires sur Startup.cs dans .NET Core

Comment les gens réagissent-ils au test des unités sur leurs classes Startup.cs dans une application .NET Core 2? Toutes les fonctionnalités semblent être fournies par des méthodes d'extensions statiques qui ne sont pas fictives?

Si vous prenez cette méthode ConfigureServices par exemple:

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

    services.AddMvc();
}

Comment puis-je écrire des tests pour m'assurer que AddDbContext (...) & AddMvc () sont appelés? Le choix de mettre en œuvre toutes ces fonctionnalités via des méthodes Extensions semble l'avoir rendu impossible à tester?

15
Rob Earlam

Eh bien oui, si vous voulez vérifier le fait que la méthode d'extension AddDbContext a été appelée le services, vous avez des problèmes . La bonne chose, c'est que vous ne devriez pas réellement vérifier ce fait.

Startup class est une application composition root . Et lors du test d'une racine de composition, vous souhaitez vérifier qu'elle enregistre effectivement toutes les dépendances requises pour l'instanciation des objets racine (contrôleurs dans le cas d'une application ASP.NET Core).

Supposons que vous ayez le contrôleur suivant:

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

Vous pouvez essayer de vérifier si Startup a enregistré le type pour ISomeDependency. Mais la mise en œuvre de ISomeDependency pourrait également nécessiter d'autres dépendances que vous devriez vérifier . Finalement, vous vous retrouvez avec un test contenant de nombreuses vérifications pour différentes dépendances, mais cela ne garantit pas pour autant que la résolution d'objet ne lève pas l'exception de dépendance manquante. Il n'y a pas trop de valeur dans un tel test.

Une approche qui fonctionne bien pour moi lors du test d’une racine de composition consiste à utiliser un conteneur d’injection de dépendance réel. J'appelle ensuite une racine de composition dessus et affirme que la résolution de l'objet racine ne se déclenche pas.

Il ne peut pas être considéré comme un test unitaire pur, car nous utilisons une autre classe non tronquée. Mais de tels tests, contrairement à d’autres tests d’intégration, sont rapides et stables. Et le plus important, ils apportent la valeur du contrôle valide pour un enregistrement correct des dépendances. Si ce test réussit, vous pouvez être sûr que cet objet sera également instancié correctement dans le produit.

Voici un exemple de ce test:

[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

Cette approche fonctionne et utilise le véritable pipeline MVC, car les choses ne devraient être simulées que si vous devez modifier leur fonctionnement. 

    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