XUnitリポジトリテストにInMemory EF7データベースを使用しようとしています。
しかし、私の問題は、作成したコンテキストを破棄しようとすると、メモリ内のデータベースが保持されることです。これは、1つのテストに他のテストが含まれることを意味します。
私はこの記事を読んだ メモリ内データストアを使用した単体テストエンティティフレームワーク7 で、TestClassのコンストラクターでコンテキストをセットアップしようとしました。しかし、このアプローチは機能しません。テストを個別に実行するとすべてが正常ですが、最初のテストメソッドはDBに何かを追加し、2番目のテストメソッドは前のテストメソッドのダーティDBで開始します。テストクラスにIDispose
を追加してみますが、メソッドDatabaseContextとDBはメモリに保持されます。私は何を間違っているのですか?
私のコードは次のようになります:
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);
}
}
}
使っています:
"Microsoft.EntityFrameworkCore.InMemory": "1.0.0"
"Microsoft.EntityFrameworkCore": "1.0.0"
"xunit": "2.2.0-beta2-build3300"
documentation から、
通常、EFは、AppDomainの特定のタイプのすべてのコンテキストに対して単一の
IServiceProvider
を作成します。つまり、すべてのコンテキストインスタンスが同じInMemoryデータベースインスタンスを共有します。 1つが渡されるようにすることで、InMemoryデータベースのスコープを制御できます。
テストクラスを使い捨てにしてデータコンテキストをそのように破棄する代わりに、テストごとに新しいコンテキストを作成します。
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;
}
次に、各テストで、このメソッドを使用してデータコンテキストを新規作成します。
using (var context = new DatabaseContext(CreateNewContextOptions()))
{
// Do all of your data access and assertions in here
}
このアプローチにより、テストごとにきしむクリーンなメモリ内データベースが得られます。
ネイトの答えは今時代遅れかもしれないし、何か間違ったことをしているかもしれません。 UseInMemoryDatabase()
にdb名が必要になりました。
以下は私が最終的に何をしたかです。一意のデータベース名を作成する行を追加しました。コンストラクターを使用するためにusingステートメントを削除し、各テストケースで一度呼び出されるdisposeを実行しました。
私のテストからそこにいくつかのデバッグ行があります。
public class DeviceRepositoryTests : IClassFixture<DatabaseFixture>, IDisposable
{
private readonly DeviceDbContext _dbContext;
private readonly DeviceRepository _repository;
private readonly ITestOutputHelper _output;
DatabaseFixture _dbFixture;
public DeviceRepositoryTests(DatabaseFixture dbFixture, ITestOutputHelper output)
{
this._dbFixture = dbFixture;
this._output = output;
var dbOptBuilder = GetDbOptionsBuilder();
this._dbContext = new DeviceDbContext(dbOptBuilder.Options);
this._repository = new DeviceRepository(_dbContext);
DeviceDbContextSeed.EnsureSeedDataForContext(_dbContext);
//_output.WriteLine($"Database: {_dbContext.Database.GetDbConnection().Database}\n" +
_output.WriteLine($"" +
$"Locations: {_dbContext.Locations.Count()} \n" +
$"Devices: {_dbContext.Devices.Count()} \n" +
$"Device Types: {_dbContext.DeviceTypes.Count()} \n\n");
//_output.WriteLine(deviceDbContextToString(_dbContext));
}
public void Dispose()
{
_output.WriteLine($"" +
$"Locations: {_dbContext.Locations.Count()} \n" +
$"Devices: {_dbContext.Devices.Count()} \n" +
$"Device Types: {_dbContext.DeviceTypes.Count()} \n\n");
_dbContext.Dispose();
}
private static DbContextOptionsBuilder<DeviceDbContext> GetDbOptionsBuilder()
{
// The key to keeping the databases unique and not shared is
// generating a unique db name for each.
string dbName = Guid.NewGuid().ToString();
// 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<DeviceDbContext>();
builder.UseInMemoryDatabase(dbName)
.UseInternalServiceProvider(serviceProvider);
return builder;
}
これは非常に基本的なテストケースです。
[Fact]
public void LocationExists_True()
{
Assert.True(_repository.LocationExists(_dbFixture.GoodLocationId));
}
また、同じIDの同じデバイスを削除しようとして、それぞれが合格したテストケースを8つ作成しました。