Testen mit InMemoryTesting with InMemory

Der InMemory-Anbieter ist nützlich, wenn Sie Komponenten verwenden, indem Sie die Verbindung zur echten Datenbank, ohne den Aufwand für die tatsächliche Datenbankvorgänge Tools, testen möchten.The InMemory provider is useful when you want to test components using something that approximates connecting to the real database, without the overhead of actual database operations.

Tipp

Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.You can view this article's sample on GitHub.

InMemory ist nicht mit einer relationalen DatenbankInMemory is not a relational database

EF Core-Datenbankanbieter keine relationalen Datenbanken sein.EF Core database providers do not have to be relational databases. InMemory fungiert als ein allgemeiner-Datenbank zu Testzwecken und dient nicht zum imitieren von einer relationalen Datenbank.InMemory is designed to be a general purpose database for testing, and is not designed to mimic a relational database.

Beispiele hierfür sind:Some examples of this include:

  • InMemory können Sie zum Speichern von Daten, die Einschränkungen der referenziellen Integrität in einer relationalen Datenbank verletzen würde.InMemory will allow you to save data that would violate referential integrity constraints in a relational database.
  • Wenn Sie DefaultValueSql(string) für eine Eigenschaft im Modell verwenden, dies ist eine relationale Datenbank-API und hat keine Auswirkungen, bei der Ausführung für InMemory.If you use DefaultValueSql(string) for a property in your model, this is a relational database API and will have no effect when running against InMemory.
  • Parallelität über Zeitstempel/Zeilenversion ([Timestamp] oder IsRowVersion) wird nicht unterstützt.Concurrency via Timestamp/row version ([Timestamp] or IsRowVersion) is not supported. Keine DbUpdateConcurrencyException wird ausgelöst, wenn ein Update erfolgt mit einem alten Concurrency-Token.No DbUpdateConcurrencyException will be thrown if an update is done using an old concurrency token.

Tipp

Für viele Zwecke testen werden diese Unterschiede nicht wichtig.For many test purposes these differences will not matter. Wenn Sie mit dem Sie etwas zu testen, die mehr wie eine "true" relationale Datenbank verhält sich möchten, klicken Sie dann sollten Sie jedoch SQLite-in-Memory-Modus.However, if you want to test against something that behaves more like a true relational database, then consider using SQLite in-memory mode.

Beispiel-SzenarioExample testing scenario

Betrachten Sie den folgenden Dienst, mit dem Anwendungscode zum Durchführen bestimmter Vorgänge im Zusammenhang mit Blogs zu können.Consider the following service that allows application code to perform some operations related to blogs. Intern verwendet eine DbContext , die eine Verbindung mit SQL Server-Datenbank her.Internally it uses a DbContext that connects to a SQL Server database. Es wäre nützlich, um diesem Kontext für die Verbindung eine InMemory-Datenbank, damit wir können effiziente Tests für diesen Dienst schreiben, ohne den Code ändern zu müssen, oder einen Großteil der Arbeit beim Erstellen eines Tests Austauschen des Kontexts double.It would be useful to swap this context to connect to an InMemory database so that we can write efficient tests for this service without having to modify the code, or do a lot of work to create a test double of the context.

using System.Collections.Generic;
using System.Linq;

namespace BusinessLogic
{
    public class BlogService
    {
        private BloggingContext _context;

        public BlogService(BloggingContext context)
        {
            _context = context;
        }

        public void Add(string url)
        {
            var blog = new Blog { Url = url };
            _context.Blogs.Add(blog);
            _context.SaveChanges();
        }

        public IEnumerable<Blog> Find(string term)
        {
            return _context.Blogs
                .Where(b => b.Url.Contains(term))
                .OrderBy(b => b.Url)
                .ToList();
        }
    }
}

Bereiten Sie den KontextGet your context ready

Vermeiden Sie die Konfiguration von zwei DatenbankanbieterAvoid configuring two database providers

In den Tests wird den Kontext zum Verwenden des InMemory-Anbieters extern konfigurieren.In your tests you are going to externally configure the context to use the InMemory provider. Wenn Sie durch das Überschreiben ein Datenbankanbieters konfigurieren OnConfiguring im Kontext Ihrer müssen Sie fügen der bedingten Code, um sicherzustellen, dass Sie nur den Datenbankanbieter konfigurieren, wenn eine nicht bereits konfiguriert wurde.If you are configuring a database provider by overriding OnConfiguring in your context, then you need to add some conditional code to ensure that you only configure the database provider if one has not already been configured.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFProviders.InMemory;Trusted_Connection=True;ConnectRetryCount=0");
    }
}

Tipp

Wenn Sie ASP.NET Core verwenden, sollten dann nicht dieser Code erforderlich, da Ihr Datenbankanbieter außerhalb des Kontexts (in "Startup.cs") bereits konfiguriert ist.If you are using ASP.NET Core, then you should not need this code since your database provider is already configured outside of the context (in Startup.cs).

Fügen Sie einen Konstruktor für das TestenAdd a constructor for testing

Die einfachste Möglichkeit zum Aktivieren von Tests mit einer anderen Datenbank ist so ändern Sie den Kontext, um einen Konstruktor verfügbar machen, die akzeptiert eine DbContextOptions<TContext>.The simplest way to enable testing against a different database is to modify your context to expose a constructor that accepts a DbContextOptions<TContext>.

public class BloggingContext : DbContext
{
    public BloggingContext()
    { }

    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

Tipp

DbContextOptions<TContext> Gibt dem Kontext alle zugehörigen Einstellungen, z. B. welche Datenbank für die Verbindung.DbContextOptions<TContext> tells the context all of its settings, such as which database to connect to. Dies ist das gleiche Objekt, das durch Ausführen der OnConfiguring-Methode im Kontext Ihrer basiert.This is the same object that is built by running the OnConfiguring method in your context.

Schreiben von testsWriting tests

Die Taste, um Tests mit dieser Anbieter ist die Möglichkeit, teilen Sie den Kontext den InMemory-Anbieter, und Steuern des Gültigkeitsbereichs der in-Memory-Datenbank.The key to testing with this provider is the ability to tell the context to use the InMemory provider, and control the scope of the in-memory database. Möchten Sie in der Regel eine fehlerfreie Datenbank, für jede Testmethode.Typically you want a clean database for each test method.

Hier ist ein Beispiel für eine Testklasse, die der InMemory-Datenbank verwendet.Here is an example of a test class that uses the InMemory database. Jede Testmethode gibt einen eindeutigen Datenbanknamen an, was bedeutet, dass jede Methode ihre eigene InMemory-Datenbank verfügt.Each test method specifies a unique database name, meaning each method has its own InMemory database.

Tipp

Verwenden der .UseInMemoryDatabase() Erweiterungsmethode Verweis das NuGet-Paket Microsoft.EntityFrameworkCore.InMemory.To use the .UseInMemoryDatabase() extension method, reference the NuGet package Microsoft.EntityFrameworkCore.InMemory.

using BusinessLogic;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;

namespace TestProject.InMemory
{
    [TestClass]
    public class BlogServiceTests
    {
        [TestMethod]
        public void Add_writes_to_database()
        {
            var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseInMemoryDatabase(databaseName: "Add_writes_to_database")
                .Options;

            // Run the test against one instance of the context
            using (var context = new BloggingContext(options))
            {
                var service = new BlogService(context);
                service.Add("http://sample.com");
            }

            // Use a separate instance of the context to verify correct data was saved to database
            using (var context = new BloggingContext(options))
            {
                Assert.AreEqual(1, context.Blogs.Count());
                Assert.AreEqual("http://sample.com", context.Blogs.Single().Url);
            }
        }

        [TestMethod]
        public void Find_searches_url()
        {
            var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseInMemoryDatabase(databaseName: "Find_searches_url")
                .Options;

            // Insert seed data into the database using one instance of the context
            using (var context = new BloggingContext(options))
            {
                context.Blogs.Add(new Blog { Url = "http://sample.com/cats" });
                context.Blogs.Add(new Blog { Url = "http://sample.com/catfish" });
                context.Blogs.Add(new Blog { Url = "http://sample.com/dogs" });
                context.SaveChanges();
            }

            // Use a clean instance of the context to run the test
            using (var context = new BloggingContext(options))
            {
                var service = new BlogService(context);
                var result = service.Find("cat");
                Assert.AreEqual(2, result.Count());
            }
        }
    }
}