2016-12-27 2 views
2

Ich habe ein WebAPI erstellt und neben meinen Tests auf Postman möchte ich einige Integration/Unit-Tests implementieren.XUnit DI durch überschreiben Startup-Datei (.net core)

Jetzt ist meine Geschäftslogik sehr dünn, die meiste Zeit ist es mehr von CRUD-Aktionen, daher wollte ich mit dem Testen meiner Controller beginnen.

Ich habe eine Grundeinstellung. Repository-Muster (Schnittstellen), Services (Business-Logik) und Controller. Der Ablauf geht Controller (DI Service) -> Service (DI Repo) -> Repo Aktion!

Also was ich getan habe, war überschreiben meine Startup-Datei in eine in Speicher-Datenbank zu ändern und der Rest sollte gut sein (ich würde annehmen) Dienste hinzugefügt werden, Repos hinzugefügt werden und jetzt bin ich in eine im Speicher DB, was ist gut für meine grundlegenden Tests.

namespace API.UnitTests 
{  
    public class TestStartup : Startup 
    { 
     public TestStartup(IHostingEnvironment env) 
      : base(env) 
     { 

     } 

     public void ConfigureTestServices(IServiceCollection services) 
     { 
      base.ConfigureServices(services); 
      //services.Replace<IService, IMockedService>(); 
     } 

     public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 
     { 
      base.Configure(app, env, loggerFactory); 
     } 

     public override void SetUpDataBase(IServiceCollection services) 
     { 
      var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" }; 
      var connectionString = connectionStringBuilder.ToString(); 
      var connection = new SqliteConnection(connectionString); 

      services 
       .AddEntityFrameworkSqlite() 
       .AddDbContext<ApplicationDbContext>(
        options => options.UseSqlite(connection) 
       ); 
     } 
    } 
} 

schrieb ich meinen ersten Test, aber die DatasourceService nicht da ist:

Folgende Konstruktorparameter haben nicht passende Fixturedaten: DatasourceService datasourceService

namespace API.UnitTests 
{ 
    public class DatasourceControllerTest 
    { 
     private readonly DatasourceService _datasourceService; 

     public DatasourceControllerTest(DatasourceService datasourceService) 
     { 
      _datasourceService = datasourceService;    
     } 

     [Xunit.Theory, 
     InlineData(1)] 
     public void GetAll(int companyFk) { 
      Assert.NotEmpty(_datasourceService.GetAll(companyFk)); 
     } 
    } 
} 

Was bin Ich vermisse?

+0

iirc können Sie nicht Dependency Injection auf Testklassen verwenden . Sie können nur xunit spezielle Fixtures über Konstruktor (https://xunit.github.io/docs/shared-context.html siehe Klassen- und Collection-Fixtures) injizieren lassen. Für Integrationstests müssen Sie eine Instanz von IServiceProvider erhalten und Ihren Service auflösen lassen. Für Controller-Tests müssen Sie 'TestServer' Klasse verwenden => docs.microsoft.com/en-us/aspnet/core/testing/integration-testing – Tseng

+0

Auch möchten Sie vielleicht nicht von Startup.cs erben und stattdessen eine separate Klasse haben . mit dem Bootstrapping-Code. Überschreiben funktioniert nicht gut mit bestimmten Konfigurationen (d. h. wenn Code nach A aber vor B ausgeführt werden muss). Zweimal die gleiche Schnittstelle zu registrieren kann beim Aufruf von 'GetRequiredService' zu ​​Ausnahmen führen, weil mehr als eine registriert ist (es sei denn, Sie verwenden' services.TryAddXxx () ' – Tseng

+0

) Wenn die Testklassen Zugriff auf die Fixture-Instanz, fügen Sie es als Konstruktor-Argument, und es wird automatisch zur Verfügung gestellt. "so Fixtures können DI verwenden, aber Sie können nicht DI von Start - gut das ist eine Schande. Nun natürlich Registrierung zweimal usw. sind zu erwarten. Aber Wenn ich noch DI durch Start in Tests hätte, dann hätte ich meinen Test in wenigen Sekunden gestartet. – Drakoumel

Antwort

3

Sie können die Abhängigkeitsinjektion für Testklassen nicht verwenden. Sie können nur xunit spezielle Fixtures über Konstruktor injizieren lassen (siehe docs).

Für Integrationstests möchten Sie die TestServer Klasse aus Microsoft.AspNetCore.TestHost Paket und eine separate Startup.cs Klasse verwenden (einfacher Konfiguration als Vererbung imho).

public class TestStartup : Startup 
{ 
    public TestStartup(IHostingEnvironment env) 
    { 
     var builder = new ConfigurationBuilder() 
      .SetBasePath(env.ContentRootPath) 
      .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 
      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 
      .AddEnvironmentVariables(); 
     Configuration = builder.Build(); 
    } 

    public IConfigurationRoot Configuration { get; } 

    public void ConfigureTestServices(IServiceCollection services) 
    { 
     services.Replace(ServiceDescriptor.Scoped<IService, MockedService>()); 
     services.AddEntityFrameworkSqlite() 
      .AddDbContext<ApplicationDbContext>(
       options => options.UseSqlite(connection) 
      ); 
    } 

    public void Configure(IApplicationBuilder app) 
    { 
     // your usual registrations there 
    } 
} 

In Ihrem Unit-Test-Projekt, müssen Sie eine Instanz der TestServer und führen den Test erstellen.

public class DatasourceControllerTest 
{ 
    private readonly TestServer _server; 
    private readonly HttpClient _client; 

    public DatasourceControllerTest() 
    { 
     // Arrange 
     _server = new TestServer(new WebHostBuilder() 
      .UseStartup<TestStartup>()); 
     _client = _server.CreateClient(); 
    } 

    [Xunit.Theory, 
    InlineData(1)] 
    public async Task GetAll(int companyFk) { 
     // Act 
     var response = await _client.GetAsync($"/api/datasource/{companyFk}"); 
     // expected result from rest service 
     var expected = @"[{""data"":""value1"", ""data2"":""value2""}]"; 

     // Assert 
     // This makes sure, you return a success http code back in case of 4xx status codes 
     // or exceptions (5xx codes) it throws an exception 
     response.EnsureSuccessStatusCode(); 

     var resultString = await response.Content.ReadAsStringAsync(); 
     Assert.Equals(resultString, expectedString); 
    } 
} 

Nun, wenn Sie bestimmte Vorgänge aufrufen, die in die Datenbank schreiben, können Sie auch prüfen, ob die Daten wirklich in die Datenbank geschrieben wird:

[Xunit.Theory, 
InlineData(1)] 
public async Task GetAll(int companyFk) { 
    // Act 
    var response = await _client.DeleteAsync($"/api/datasource/{companyFk}"); 
    // expected result from rest service 

    // Assert 
    response.EnsureSuccessStatusCode(); 

    // now check if its really gone in the database. For this you need an instance 
    // of the in memory Sqlite DB. TestServer has a property Host, which is an IWebHost 
    // and it has a property Services which is the IoC container 

    var provider = _server.Host.Services; 
    var dbContext = provider.GetRequiredService<ApplicationDbContext>(); 

    var result = await dbContext.YourTable.Where(entity => entity.Id == companyFk).Any(); 

    // if it was deleted, the query should result in false 
    Assert.False(result); 
} 
+0

Sie haben gerade auch meinen Kommentar zu meiner Frage beantwortet :) Ich werde versuchen, dies später am Abend zu implementieren . Vielen Dank, das wird sehr helfen! – Drakoumel

+0

Wie kann ich 'TestServer' dazu bringen,' ConfigureTestServices' anstatt der Basisklasse ''ConfigureServices' zu verwenden? –

+0

@DmytroBogatov: Verwenden Sie '.UseEnvironment (" Test ")' auf dem 'WebHostBuilder' – Tseng

Verwandte Themen