InMemory のテストTesting with InMemory

InMemory プロバイダーは、実際のデータベース操作のオーバーヘッドがなく、実際のデータベースへの接続を近似するものを使用してコンポーネントをテストする場合に便利です。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.

ヒント

この記事のサンプルは GitHub で確認できます。You can view this article's sample on GitHub.

InMemory はリレーショナル データベースではありません。InMemory is not a relational database

EF Core データベース プロバイダーはリレーショナル データベースではありません。EF Core database providers do not have to be relational databases. InMemory ように、テスト用の汎用データベースを設計し、リレーショナル データベースを模倣するためのものではありません。InMemory is designed to be a general purpose database for testing, and is not designed to mimic a relational database.

次のとおりのいくつかの例では:Some examples of this include:

  • InMemory はリレーショナル データベースの参照整合性制約に違反するデータを保存できます。InMemory will allow you to save data that would violate referential integrity constraints in a relational database.
  • モデルのプロパティを DefaultValueSql(string) を使用する場合は、これは、リレーショナル データベース API であり、効果はありません 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.
  • タイムスタンプや行のバージョンを使用して同時実行([Timestamp]またはIsRowVersion) はサポートされていません。Concurrency via Timestamp/row version ([Timestamp] or IsRowVersion) is not supported. いいえDbUpdateConcurrencyException更新が行われた場合にスローされます古いの同時実行トークンを使用します。No DbUpdateConcurrencyException will be thrown if an update is done using an old concurrency token.

ヒント

多くのテスト目的でのこれらの違いは関係ありません。For many test purposes these differences will not matter. ただし、真のリレーショナル データベースのように動作するものをテストする場合は、使用を検討してSQLite メモリ内モードします。However, if you want to test against something that behaves more like a true relational database, then consider using SQLite in-memory mode.

テスト シナリオの例Example testing scenario

ブログに関連するいくつかの操作を実行するアプリケーション コードは、次のサービスを検討してください。Consider the following service that allows application code to perform some operations related to blogs. 内部的には、 DbContext SQL Server データベースに接続します。Internally it uses a DbContext that connects to a SQL Server database. ように、コードを変更しなくてもこのサービスの効率的なテストを記述またはテストを作成する作業の多くを実行できます InMemory データベースに接続するには、このコンテキストを交換する便利なことが、コンテキストの二重。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();
        }
    }
}

コンテキストを準備します。Get your context ready

2 つのデータベース プロバイダーを構成しない場合Avoid configuring two database providers

テストでは、InMemory プロバイダーを使用するコンテキストの外部で構成すること。In your tests you are going to externally configure the context to use the InMemory provider. オーバーライドすることで、データベース プロバイダーを構成している場合OnConfiguringコンテキストでする必要がある 1 つ既に構成されていない場合のみ、データベース プロバイダーを構成することを確認する条件付きコードを追加します。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");
    }
}

ヒント

ASP.NET Core を使用している場合、必要はありませんこのコードは、データベース プロバイダーが既に (Startup.cs) のコンテキスト外で構成されているためです。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).

テストのコンス トラクターを追加します。Add a constructor for testing

別のデータベースに対するテストを有効にする最も簡単な方法が変更のコンテキストを受け取るコンス トラクターを公開するには、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)
    { }

ヒント

DbContextOptions<TContext> すべてに接続するデータベースなど、その設定のコンテキストに指示します。DbContextOptions<TContext> tells the context all of its settings, such as which database to connect to. これは、コンテキストの OnConfiguring メソッドを使用して構築された同じオブジェクトです。This is the same object that is built by running the OnConfiguring method in your context.

テストの記述Writing tests

このプロバイダーでのテストにキーをメモリ内データベースのスコープを制御、InMemory プロバイダーを使用してコンテキストを通知する機能があります。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. 通常、クリーンなデータベースは、各テスト メソッドにします。Typically you want a clean database for each test method.

InMemory データベースを使用するテスト クラスの例を次に示します。Here is an example of a test class that uses the InMemory database. 各テスト メソッドでは、一意のデータベース名、つまり各メソッドが、独自の InMemory データベースを指定します。Each test method specifies a unique database name, meaning each method has its own InMemory database.

ヒント

使用する、.UseInMemoryDatabase()参照、NuGet パッケージの拡張メソッドで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());
            }
        }
    }
}