Непонятки с EF
Играюсь я тут дома с прогой. НЕТ 8.0
Простой поиск
_dbContext.Users.FirstOrDefault(u => u.Email == email);
Выдаёт непонятную ошибку.
"
Microsoft.Data.SqlClient.SqlException
Invalid column name 'UserId'.
at Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
...
at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
...
"
При этом юнит тесты с репозиторием и реальной базой работают без проблем. (И с тем же db Context классом)
А вот интеграционный тест выдает уже ошибку, правда сделал на уровень выше с DI и инициализацией базы как в приложении.
Какие будут идеи?
База генерится так
public sealed class UserConfigurations : IEntityTypeConfiguration<User> { public void Configure(EntityTypeBuilder<User> builder) { ConfigureUsersTable(builder); } private void ConfigureUsersTable(EntityTypeBuilder<User> builder) { builder .ToTable("Users"); builder .HasKey(u => u.Id); builder .Property(u => u.Id) //.HasColumnName("UserId") .ValueGeneratedNever(); //.HasConversion( // id => id.Value, // value => UserId.Create(value)); ...
С этой строкой не работает .HasColumnName("UserId")
Конверсия сделана глобальной - internal class UserIdConverter : ValueConverter
public sealed class User : AggregateRoot<UserId, Guid> { ... private User( UserId id,
оказалось в строке подключения, где указана другая база?
в принципе да, но не всё так просто было.
Для запуска в проекте миграции нужен IDesignTimeDbContextFactory со своей строкой подключения в проекте "с базой"
А для запуска приложения используется другая строка подключения, обе строки должны быть идентичны. /Как их брать их одного места пока не придумал /
Поэтому база создавалась в одном месте и юнит тесты дергали ее /для тестов тоже своя строка подключения/, а вот старт приложения или интеграционного теста пользовал другую строку соединения.
Как только тест начинает лезть в БД это уже не юнит тест, а интеграционный
тут несколько пунктов:
1. Мокать базу смысла не вижу. Обычно база в памяти, не фонтан, но некоторые ошибки можно найти
2. Именно в данном случае, специально использовал реальную базу для дополнительной проверки в чем может быть проблема.
3. Интеграционный тест тоже есть, но он не на уровне репозитория, а "выше"
Хотя сейчас больше интересует другой вопрос как отмапить ссылки на агрегаты по ИД, если ИД - value object
Дверь надо называть дверью
По теории вполне возможно. Но если на практике она долгое время называлась дыркой и выглядит как дырка, то никто не захочет называть это дверью Разве в проспекте можно написать - дверь в загадочное подземелье...
Если Moq есть, то нефиг называть энто интеграционным тестом.
ну и напрямую ничего про базы нет, кроме side effect.
"Unit tests focus on a single part of a whole application in total isolation, usually, a single class or function. Ideally, the tested component is free of side effects so it is as easy to isolate and test as possible."
"We have learned that, in practice, the isolation property of unit tests may not be enough for some functions. In this case, one solution is to test how parts of the application work together as a whole. This approach is called integration testing.
Unlike unit testing, integration testing considers side effects from the beginning. These side effects may even be desirable."
ну и напрямую ничего про базы нет, кроме side effect.
Мна... Это как? Надо чтобы где-то было написано "юнит тесты не должны использовать базу данных"? И тогда ты поверишь что они действительно это не должны делать?
Не надо искать что-то в "side effects". "...in total isolation, usually, a single class or function".
Если Moq есть, то нефиг называть энто интеграционным тестом.
Неправильно. Во-первых: Mock. Во-вторых: представь, что у тебя три системы (сервиса) А, Б, В. Код из А использует Б и В. Сначала Б, потом В. И ты хочешь проверить интеграцию А с В. Конечно же, ты можешь (и даже должен) замОчить вызов Б. От этого тест не перестал быть интеграционным и стал намного более информативным. Если он сломался - не надо гадать, что у тебя накрылось, Б или В. Ты сразу знаешь что накрылось В.
in total isolation
ладно пойдем от этого... Вот например функция и еще много подобных и посложнее. _dbContext другой класс, значит его нужно чем то заменить. Чем и как?
и как будем после тестировать сам _dbContext? И какой будет толк во всём этом разделении? Не чисто теоретически сервис А и сервис Б, а совершенно практически.
И что изменится если вместо одного названия MyRepo.UnitTest будет другое MyRepo.IntegrationTest? При этом все остальные IntegrationTest используют части "живой системы".
public async Task<Group?> GetMyGroupAsync(UserId ownerId, CancellationToken cancellationToken) { Group? group = await _dbContext.Groups .Include(a => a.Owner) .Include(a => a.Users) .Include(a => a.Information) .FirstOrDefaultAsync(x => x.Owner.Id == ownerId, cancellationToken); return group; }
Это очень хороший метод. Для юнит-тестов. Потому что для него юнит-тесты писать не надо. Опа. Он просто "обёртка" вокруг вызова внешней системы. Его надо тестировать интеграционным тестом. Который проверит, что с определённой тестовой БД, в которой есть (например) 10 записей, при поиске с UserId = 123 мы найдём ровно том, что ожидали.
А вот когда мы будем писать юнит-тесты для кода, который вызывает этот метод, мы будем мочить его вызовы. Сможем тестировать код, вызывающий наш гет без БД.
Потому что для него юнит-тесты писать не надо.
Из этого можно сделать вывод, что для всех классов подобного типа не следует писать юнит тесты их просто следует называть интеграционными тестами.
Что меняется от использования другого названия? Тестироваться то будет ОДИН класс, который также написан нами. То есть формально будет юнит тест. Или нужно делать упор на "внешнюю систему", но ведь ее мы не должны тестировать, по умолчанию она работает "правильно".
что с определённой тестовой БД
Этот подход можно пользовать если мы хотим тестировать исключительно Query, а вот с Command будет посложнее, пусть даже база будет в локальном докере.
при поиске с UserId = 123 мы найдём ровно то, что ожидали
Это самое малое что меня будет интересовать в данном случае. А вот загрузится ли полностью владелец или пользователи будет гораздо интереснее.
А если там еще и джойны есть, то есть что проверять.
Статическая база никак не подходит.
А вот когда мы будем писать юнит-тесты для кода, который вызывает этот метод, мы будем мочить его вызовы.
С этим никаких вопросов нет, именно так и делается
нп
А есть какие-то преимущества/недостатки хранения экземпляра dbContext в свойстве класса?
Первое, что приходит на ум, что вот такие вещи будут действовать во всех потоках.
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.LazyLoadingEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
А есть какие-то преимущества/недостатки хранения экземпляра dbContext в свойстве класса?
Что то не припоминаю подобного вопроса. А что предлагаете вместо этого?
public class MyRepository { private readonly MyDbContext _dbContext; public MyRepository(MyDbContext dbContext) { _dbContext = dbContext; }
_dbContext
Подчёркивания - давно дурной тон. Как и всякие префиксы b перед булевыми переменными, e перед перечислениями и т.п. IDE давно всё как надо подсвечивает и выдаёт мгновенные всплывающие подсказки по типам переменных и их принадлежности. Зачем захламлять код закорючками, которые помогали, когда IDE были лишь блокнотами с прикрученными компиляторами?