Я работаю над веб-приложением ASP.Net Core MVC.
Мое решение содержит 2 проекта:
Я добавил ссылку на проект приложения в проект Tests.
Сейчас я хочу написать класс в проекте XUnit Tests, который будет связываться с базой данных через структуру сущностей.
Что я делал в своем проекте приложения, так это доступ к моему классу DbContext
через внедрение зависимостей конструктора.
Но я не могу этого сделать в моем тестовом проекте, потому что у меня нет файла Startup.cs
. В этом файле я могу объявить, какие услуги будут доступны.
Итак, что я могу сделать, чтобы получить ссылку на экземпляр моего DbContext
в тестовом классе?
Вы можете реализовать своего собственного поставщика услуг для решения проблемы DbContext
.
public class DbFixture
{
public DbFixture()
{
var serviceCollection = new ServiceCollection();
serviceCollection
.AddDbContext<SomeContext>(options => options.UseSqlServer("connection string"),
ServiceLifetime.Transient);
ServiceProvider = serviceCollection.BuildServiceProvider();
}
public ServiceProvider ServiceProvider { get; private set; }
}
public class UnitTest1:IClassFixture<DbFixture>
{
private ServiceProvider _serviceProvider;
public UnitTest1(DbFixture fixture)
{
_serviceProvider = fixture.ServiceProvider;
}
[Fact]
public void Test1()
{
using (var context = _serviceProvider.GetService<SomeContext>())
{
}
}
}
Но имейте в виду, что использование EF внутри модульного теста - не лучшая идея, и лучше имитировать DbContext.
Анатомия хорошего модульного тестирования
Ошибка копирования и вставки. Вы можете поместить DbFixture
в любом месте тестового проекта. Я создаю папку TestSetup
или TestInfrastructure
и помещаю в эти папки такие классы.
Вместо вашей строки с AddEntityFrameworkSqlServer я использую это: serviceCollection.AddDbContext <BDDContext> (options => options.UseSqlServer ("connection string") Есть большая разница?
Нет, оба работают нормально и AddEntityFrameworkSqlServer
регистрирует дополнительные сервисы.
Еще один момент: вы уверены, что мне не нужно где-то «объявлять» этот класс DbFixture? У меня есть предупреждение, в котором говорится, что класс никогда не создается, и у меня есть ошибка времени выполнения: Следующие параметры конструктора не имеют совпадающих данных фикстуры: DbFixture fixture
привет @MohsenEsmailpour, можете ли вы ответить на этот вопрос здесь, мне нужно захватить все службы и использовать их по вызову для интеграции дальше, а не только контекст базы данных, спасибо! stackoverflow.com/questions/57331395/…
@AlanWalker Конечно, я отвечу на вопрос как можно скорее.
Пытался создать образец, но не запустился (LinqPad 6: Query -> Add XUnit Test Support, а затем вставил свой код; обычно это хорошая отправная точка для тестирования). Как ссылаться на ServiceCollection, ServiceLifetime, ServiceProvider? Не могли бы вы дать короткий отрывок для SomeContext?
Анатомия хорошего модульного тестирования - к сожалению, предоставленная вами ссылка не работает, не могли бы вы найти ее и исправить, пожалуйста?
.UseSqlServer
требует загрузки пакета Microsoft.EntityFrameworkCore.SqlServer
. Это связано с сообщила о проблеме № 7891 с EF Core..
Хорошо, чтобы проверить это, я использовал Adventureworks, добавил класс SomeContext
с public SomeContext(DbContextOptions options) : base(options)
и public DbSet<AWBuildVersion> AWBuildVersions { get; set; }
, реализовал AWBuildVersion
со всеми свойствами и добавил запрос context.AWBuildVersions.Select(s=>s.DatabaseVersion).FirstOrDefault().Dump();
в оператор using во фрагменте кода. Этот запрос выдает ошибку. Тип объекта AWBuildVersion требует определения первичного ключа. «Но я использовал атрибут [Key] для SystemInformationID
. Вы знаете, как заставить его работать?
После использования аннотаций [Key], [Table ("TableName")] и [Column ("ColumName"] он все еще не работал. Затем я выяснил, почему: я определил свойства в классе таблицы "как есть", т.е. без { get; set; }
- после добавления что работает нормально!
@Matt Я исправил неработающую ссылку.
@MohsenEsmailpour - Спасибо, это действительно хорошее резюме о том, как правильно разрабатывать (писать) модульные тесты.
Для модульных тестов вам нужно издеваться над своим контекстом.
Есть отличный пакет nuget для насмешек, который называется Moq.
Некоторая помощь для начала работы:
public ClassName : IDisposable
{
private SomeClassRepository _repository;
private Mock<DbSet<SomeClass>> _mockSomeClass;
public ClassName()
{
_mockSomeClass = new Mock<DbSet<SomeClass>>();
var mockContext = new Mock<IApplicationDbContext>();
mockContext.SetupGet(c => c.SomeClass).Returns(_mockSomeClass.Object);
_repository = new SomeClassRepository(mockContext.Object);
}
public void Dispose()
{
// Anything you need to dispose
}
[Fact]
public void SomeClassTest()
{
var someClass = new SomeClass() { // Initilize object };
_mockSomeClass.SetSource(new[] { someClass });
var result = _repository.GetSomethingFromRepo( ... );
// Assert the result
}
}
Для интеграционных тестов вы делаете то же самое, но настройки следующие:
_context = new ApplicationDbContext();
Убедитесь, что ваш TestClass
унаследован от IDisposable
(TestClass : IDisposable
), чтобы вы могли удалить контекст после каждого теста.
https://xunit.github.io/docs/shared-context
В xunit нет атрибута SetUp, atrribute. Вместо этого используйте конструктор.
Вы можете использовать Xunit.DependencyInjection
Здесь было бы очень полезно рассказать о том, что это делает и почему он лучше других. Также было бы здорово добавить небольшой образец, демонстрирующий, как он выглядит при использовании.
Вы можете использовать пакет Microsoft.EntityFrameworkCore.InMemory
var _dbContextOptions = new DbContextOptionsBuilder<DbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
А потом
var context = new DbContext(_dbContextOptions);
Что такое DatabaseSeeder? Следует ли мне поместить DbFixture в подпапку службы?