web-dev-qa-db-fra.com

Reconfigurer les dépendances lors du test d'intégration de l'API Web ASP.NET Core et d'EF Core

Je suis ce tutoriel
Test d'intégration avec Entity Framework Core et SQL Server

Mon code ressemble à ceci

Classe de test d'intégration

public class ControllerRequestsShould : IDisposable
{
    private readonly TestServer _server;
    private readonly HttpClient _client;
    private readonly YourContext _context;

    public ControllerRequestsShould()
    {
        // Arrange
        var serviceProvider = new ServiceCollection()
            .AddEntityFrameworkSqlServer()
            .BuildServiceProvider();

        var builder = new DbContextOptionsBuilder<YourContext>();

        builder.UseSqlServer($"Server=(localdb)\\mssqllocaldb;Database=your_db_{Guid.NewGuid()};Trusted_Connection=True;MultipleActiveResultSets=true")
            .UseInternalServiceProvider(serviceProvider);

        _context = new YourContext(builder.Options);
        _context.Database.Migrate();

        _server = new TestServer(new WebHostBuilder()
            .UseStartup<Startup>()
            .UseEnvironment(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")));
        _client = _server.CreateClient();
    }

    [Fact]
    public async Task ReturnListOfObjectDtos()
    {
        // Arrange database data
        _context.ObjectDbSet.Add(new ObjectEntity{ Id = 1, Code = "PTF0001", Name = "Portfolio One" });
        _context.ObjectDbSet.Add(new ObjectEntity{ Id = 2, Code = "PTF0002", Name = "Portfolio Two" });

        // Act
        var response = await _client.GetAsync("/api/route");
        response.EnsureSuccessStatusCode();


        // Assert
        var result = Assert.IsType<OkResult>(response);            
    }

    public void Dispose()
    {
        _context.Dispose();
    }

Si je comprends bien, le .UseStartUp la méthode garantit que TestServer utilise ma classe de démarrage

Le problème que j'ai est que lorsque ma déclaration de loi est frappée

var response = await _client.GetAsync("/api/route");

Je reçois une erreur dans ma classe de démarrage que la chaîne de connexion est nulle. Je pense que ma compréhension du problème est que lorsque mon contrôleur est touché par le client, il injecte mon référentiel de données, qui à son tour injecte le contexte db.

Je pense que je dois configurer le service dans le cadre du new WebHostBuilder section pour qu'il utilise le contexte créé dans le test. Mais je ne sais pas comment faire ça.

Méthode ConfigureServices dans Startup.cs

        public void ConfigureServices(IServiceCollection services)
    {
        // Add framework services
        services.AddMvc(setupAction =>
        {
            setupAction.ReturnHttpNotAcceptable = true;
            setupAction.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
            setupAction.InputFormatters.Add(new XmlDataContractSerializerInputFormatter());
        });

        // Db context configuration
        var connectionString = Configuration["ConnectionStrings:YourConnectionString"];
        services.AddDbContext<YourContext>(options => options.UseSqlServer(connectionString));

        // Register services for dependency injection
        services.AddScoped<IYourRepository, YourRepository>();
    }
19
GreenyMcDuff

Voici deux options:

1. Utilisez WebHostBuilder.ConfigureServices

Utilisation WebHostBuilder.ConfigureServices ensemble avec WebHostBuilder.UseStartup<T> pour remplacer et simuler les enregistrements DI d'une application Web:

_server = new TestServer(new WebHostBuilder()
    .ConfigureServices(services =>
    {
        services.AddScoped<IFooService, MockService>();
    })
    .UseStartup<Startup>()
);

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        //use TryAdd to support mocking IFooService
        services.TryAddTransient<IFooService, FooService>();
    }
}

Le point clé ici est d'utiliser les méthodes TryAdd à l'intérieur de la classe Startup d'origine. Douane WebHostBuilder.ConfigureServices est appelé avant l'original Startup, les mocks sont donc enregistrés avant les services d'origine. TryAdd ne fait rien si la même interface a déjà été enregistrée, donc les vrais services ne seront même pas touchés.

Plus d'informations: Exécution de tests d'intégration pour les applications ASP.NET Core .

2. Héritage/nouvelle classe de démarrage

Créez la classe TestStartup pour reconfigurer ASP.NET Core DI. Vous pouvez l'hériter de Startup et remplacer uniquement les méthodes nécessaires:

public class TestStartup : Startup
{
    public TestStartup(IHostingEnvironment env) : base(env) { }

    public override void ConfigureServices(IServiceCollection services)
    {
        //mock DbContext and any other dependencies here
    }
}

Alternativement, TestStartup peut être créé à partir de zéro pour garder les tests plus propres.

Et spécifiez-le dans UseStartup pour exécuter le serveur de test:

_server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());

Ceci est un grand exemple complet: Intégration testant votre application principale asp .net avec une base de données en mémoire .

21
Ilya Chumakov

La réponse de @ ilya-chumakov est impressionnante. Je voudrais juste ajouter une option de plus

. Utilisez la méthode ConfigureTestServices de WebHostBuilderExtensions.

La méthode ConfigureTestServices est disponible dans la version 2.1 de Microsoft.AspNetCore.TestHost (le 20.05.2018, c'est RC1-final). Et cela nous permet de remplacer les enregistrements existants par des simulacres.

Le code:

_server = new TestServer(new WebHostBuilder()
    .UseStartup<Startup>()
    .ConfigureTestServices(services =>
    {
        services.AddTransient<IFooService, MockService>();
    })
);
23
lilo.jacob