Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
247 views
in Technique[技术] by (71.8m points)

c# - How to isolate EF InMemory database per XUnit test

I am trying use InMemory EF7 database for my xunit repository test.

But my problem is that when i try to Dispose the created context the in memory db persist. It means that one test involve other.

I have read this article Unit Testing Entity Framework 7 with the In Memory Data Store and I have tried to setup the context in the constructor of my TestClass. But this approach doesn't work. When I run tests separately everything is OK, but my first test method add something into DB and second test method start with dirty DB from previous test method. I try add IDispose into test class but method DatabaseContext and DB persist in memory. What I am doing wrong am i missing something?

My code looks like:

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace Fabric.Tests.Repositories
{
    /// <summary>
    /// Test for TaskRepository 
    /// </summary>
    public class TaskRepositoryTests:IDisposable
    {

        private readonly DatabaseContext contextMemory;

        /// <summary>
        /// Constructor
        /// </summary>
        public TaskRepositoryTests()
        {
            var optionsBuilder = new DbContextOptionsBuilder<DatabaseContext>();
            optionsBuilder.UseInMemoryDatabase();
            contextMemory = new DatabaseContext(optionsBuilder.Options);

        }

        /// <summary>
        /// Dispose DB 
        /// </summary>
        public void Dispose()
        {
            //this has no effect 
            if (contextMemory != null)
            {                
                contextMemory.Dispose();
            }
        }


        /// <summary>
        /// Positive Test for ListByAssigneeId method  
        /// </summary>
        /// <returns></returns>       
        [Fact]
        public async Task TasksRepositoryListByAssigneeId()
        {
            // Arrange
            var assigneeId = Guid.NewGuid();
            var taskList = new List<TaskItem>();


            //AssigneeId != assigneeId 
            taskList.Add(new TaskItem()
            {
                AssigneeId = Guid.NewGuid(),
                CreatorId = Guid.NewGuid(),
                Description = "Descr 2",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location 2",
                Title = "Some title 2"
            });

            taskList.Add(new TaskItem()
            {
                AssigneeId = assigneeId,
                CreatorId = Guid.NewGuid(),
                Description = "Descr",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location",
                Title = "Some title"
            });

            taskList.Add(new TaskItem()
            {
                AssigneeId = assigneeId,
                CreatorId = Guid.NewGuid(),
                Description = "Descr 2",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location 2",
                Title = "Some title 2"
            });

            //AssigneeId != assigneeId 
            taskList.Add(new TaskItem()
            {
                AssigneeId = Guid.NewGuid(),
                CreatorId = Guid.NewGuid(),
                Description = "Descr 2",
                Done = false,
                Id = Guid.NewGuid(),
                Location = "Some location 2",
                Title = "Some title 2"
            });


            //set up inmemory DB            
            contextMemory.TaskItems.AddRange(taskList);

            //save context
            contextMemory.SaveChanges();

            // Act
            var repository = new TaskRepository(contextMemory);
            var result = await repository.ListByAssigneeIdAsync(assigneeId);

            // Assert
            Assert.NotNull(result.Count());

            foreach (var td in result)
            {
                Assert.Equal(assigneeId, td.AssigneeId);
            }

        }

        /// <summary>
        /// test for Add method  
        /// (Skip = "not able to clear DB context yet")
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task TasksRepositoryAdd()
        {
            var item = new TaskData()
            {
                AssigneeId = Guid.NewGuid(),
                CreatorId = Guid.NewGuid(),
                Description = "Descr",
                Done = false,
                Location = "Location",
                Title = "Title"
            };


            // Act
            var repository = new TaskRepository(contextMemory);
            var result = await repository.Add(item);

            // Assert
            Assert.Equal(1, contextMemory.TaskItems.Count());
            Assert.NotNull(result.Id);

            var dbRes = contextMemory.TaskItems.Where(s => s.Id == result.Id).SingleOrDefault();
            Assert.NotNull(dbRes);
            Assert.Equal(result.Id, dbRes.Id);
        }


    }
}

I am using:

"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"

"Microsoft.EntityFrameworkCore": "1.0.0"

"xunit": "2.2.0-beta2-build3300"
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

From the documentation,

Typically, EF creates a single IServiceProvider for all contexts of a given type in an AppDomain - meaning all context instances share the same InMemory database instance. By allowing one to be passed in, you can control the scope of the InMemory database.

Instead of making the test class disposable and trying to dispose the data context that way, create a new one for each test:

private static DbContextOptions<BloggingContext> CreateNewContextOptions()
{
    // Create a fresh service provider, and therefore a fresh 
    // InMemory database instance.
    var serviceProvider = new ServiceCollection()
        .AddEntityFrameworkInMemoryDatabase()
        .BuildServiceProvider();

    // Create a new options instance telling the context to use an
    // InMemory database and the new service provider.
    var builder = new DbContextOptionsBuilder<DatabaseContext>();
    builder.UseInMemoryDatabase()
           .UseInternalServiceProvider(serviceProvider);

    return builder.Options;
}

Then, in each test, new up a data context using this method:

using (var context = new DatabaseContext(CreateNewContextOptions()))
{
    // Do all of your data access and assertions in here
}

This approach should get you a squeaky-clean in-memory database for each test.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...