Форум программистов, компьютерный форум, киберфорум
C#: ASP.NET Core
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/6: Рейтинг темы: голосов - 6, средняя оценка - 5.00
0 / 0 / 0
Регистрация: 06.07.2021
Сообщений: 58
.NET 7

Проблема с Mock и IDbContext в тестах

03.04.2024, 10:07. Показов 1303. Ответов 8
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте. Пишу интернет магазин. Когда дошла дело до сохранения объектов в базу, выяснилось что это считается дурным тоном, сохранять объекты в базу данных сразу из контроллера. Думал переехать на MediatR и CQRS, но тогда контроллеры остаются без покрытия тестами, что мне не очень нравится, поскольку какая-то логика там присутствует, и переписывать не хочется. Было мной придумано такое решение.
Контроллер
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class HomeController : Controller
    {
        private readonly IProductsDbContext db;
        public int pageSize = 12;
        public HomeController(IProductsDbContext _db)
        {
            db = _db;
        }
        public ViewResult ListOfProducts(string category,
                                         int page = 1,
                                         SortState sortOrder = SortState.Popular)
        {
            List<Product> productPerPages;
            if (category == null)
            {
                productPerPages = db.GetAll();
            }
            else
            {
                productPerPages = db.GetByCategory(category);
            }
            ViewBag.Category = category;
            ViewBag.NameSort = sortOrder == SortState.NameAsc ? SortState.NameDesc : SortState.NameAsc;
            ViewBag.PriceSort = sortOrder == SortState.PriceAsc ? SortState.PriceDesc : SortState.PriceAsc;
 
            PageInfo pageInfo = new PageInfo
            {
                PageNumber = page,
                PageSize = pageSize,
                TotalItems = productPerPages.Count
            };
            productPerPages = sortOrder switch
            {
                SortState.Popular => productPerPages,// потом нужно добавить сюда фильтрацию
                                                     // по популярным товарам
                SortState.NameAsc => productPerPages.OrderBy(product => product.Name)
                                                      .ToList(),
                SortState.NameDesc => productPerPages.OrderByDescending(product => product.Name)
                                                      .ToList(),
                SortState.PriceAsc => productPerPages.OrderBy(product => product.Price)
                                                      .ToList(),
                SortState.PriceDesc => productPerPages.OrderByDescending(product => product.Price)
                                                      .ToList(),
            };
            productPerPages = productPerPages.Skip((page - 1) * pageSize)
                                             .Take(pageSize)
                                             .ToList();
            MainPageViewModel pageModel = new MainPageViewModel(category, productPerPages, pageInfo);
            return View(pageModel);
        }
    }
Интерфейс
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IProductsDbContext
    {
        DbSet<Product> Products { get; set; }
 
        public List<Product> GetAll()
        {
            return Products.ToList();
        }
        public List<Product> GetByCategory(string category)
        {
            return Products.Where(product => product.Category == category)
                           .ToList();
        }
        Task<int> SaveChangesAsync(CancellationToken cancellationToken);
        int SaveChanges();
    }
Тест
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[TestMethod]
        public void Can_Paginate()
        {
            Mock<IProductsDbContext> mock = new Mock<IProductsDbContext>();
            mock.Setup(x => x.Products).ReturnsDbSet(CreateTestProducts(68));
            HomeController controller = new HomeController(mock.Object);
            controller.pageSize = 33;
 
            MainPageViewModel result = (MainPageViewModel)controller.ListOfProducts(null, 3).Model;
 
            List<Product> products = result.Products.ToList();
            Assert.IsTrue(products.Count == 2);
            Assert.AreEqual(products[0].Name, "Продукт67");
            Assert.AreEqual(products[1].Name, "Продукт68");
        }
И вот вопрос
Если в контроллере я оставляю такую реализацию доступа к данным
C#
1
2
3
4
5
6
7
8
if (category == null)
            {
                productPerPages = db.GetAll();
            }
            else
            {
                productPerPages = db.GetByCategory(category);
            }
то во время выполнения тестов всплывает NullException
если же я заменяю эти строки на вот это
C#
1
2
3
4
5
6
7
8
9
if(category == null)
            {
                productPerPages = db.Products.ToList();
            }
            else
            {
                productPerPages = db.Products.Where(product => product.Category == category)
                                             .ToList();
            }
то тесты работают.

Как пофиксить?
я вижу что обьекты mock создаются но метод GetAll() не может получить к ним доступ.
а мне бы хотелось разнести методы доступа к данным в отельный .cs файл.
Буду признателен за помощь.
0
Лучшие ответы (1)
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
03.04.2024, 10:07
Ответы с готовыми решениями:

Mock свойства Database в Mock<DbContext> или получение результатов запроса к View из Mock
Хочу отделить тестирование БД от тестирования бизнеса, поэтому решил попробоваться в Mock'ах, однако столкнулся с необычной для себя...

Invalid setup on a non-virtual overridable in vb member: mock => mock[It.IsAny<string>()]
Вот такой код: public interface IGate { string Name { get; } } public interface ICommutator { ...

Тестирование MOCK
Есть интерфейс по &quot;постройке объекта&quot;. В данном случае как пример это ручка. public interface IPenBuilder { IPenBuilder...

8
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
03.04.2024, 10:20
Цитата Сообщение от paiiiko Посмотреть сообщение
mock.Setup(x => x.Products).ReturnsDbSet(CreateTestProdu cts(68));
У меня два вопроса
- что это за метод ReturnsDbSet. Помоему у Mock.а такого не было.
- ну и нет кода метода CreateTestProducts. И не понятно что за цифра 68. Может быть и данных там нет в итоге и нулл на самом деле - результат выборки коллекции.
0
0 / 0 / 0
Регистрация: 06.07.2021
Сообщений: 58
03.04.2024, 10:45  [ТС]
Цитата Сообщение от HF Посмотреть сообщение
У меня два вопроса
- что это за метод ReturnsDbSet. Помоему у Mock.а такого не было.
- ну и нет кода метода CreateTestProducts. И не понятно что за цифра 68. Может быть и данных там нет в итоге и нулл на самом деле - результат выборки коллекции.
Из NuGet пакета Moq.EntityFrameworkCore.

и метод
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static List<Product> CreateTestProducts(int quantity)
        {
            var products = new List<Product>();
            int category = 1;
 
            for (int i = 1; i <= quantity; i++)
            {
                var product = new Product
                {
                    Id = i,
                    Name = "Продукт" + i.ToString(),
                    Price = i,
                    Quantity = 1,
                    Category = "Категория" + category.ToString(),
                    Inactive = false
                };
                products.Add(product);
                category++;
                if (category > 3) category = 1;
            }
            return products;
        }
Mock заполнен объектами, просто почему то один кусок кода работает а другой нет

Добавлено через 2 минуты
ещё раз уточню, не работают именно тесты. проект запускается в обоих случаях

Добавлено через 16 минут
да, если посмотреть работу тестов в дебаге, то в
C#
1
productPerPages = db.GetAll()
мы увидим null
а в
C#
1
productPerPages = db.Products.ToList()
увидим объекты, которые создались в методе CreateTestProducts()
0
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
03.04.2024, 11:07
Цитата Сообщение от paiiiko Посмотреть сообщение
Из NuGet пакета Moq.EntityFrameworkCore.
Понятно. Мы так обычно не делаем. И я не знаю как оно работает, возможно тут и загвозка, почитайте описание, вдруг написано что ещё нужно сделать. Явно же оттуда null возвращается.

Попробуйте тогда создать свою коллекцию отдельно. А засетапить методы GetAll (которая просто вернёт всю коллекцию) и GetByCategory которая сделает выборку из этой коллекции.

P.S. Надеюсь это для теста, но db.GetAll точно не там где нужно. db - база, а GetAll относится к репозиториям и сервису. А у вас методы прямо в DbContext лежат? надеюсь это временно.

Добавлено через 1 минуту
P.S. и лучше использовать SetupGet - так как это свойство только на чтение.
0
0 / 0 / 0
Регистрация: 06.07.2021
Сообщений: 58
03.04.2024, 11:22  [ТС]
то есть такая реализация не подходит?
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Store.Domain.Interfaces;
using Store.Domain.Model;
using Microsoft.EntityFrameworkCore;
 
 
namespace Store.Domain.Data
{
    public class StoreDbContext : DbContext, ICartDbContext,
                                             IOrdersDbContext,
                                             IProductsDbContext,
                                             IReviewDbContext,
                                             IUsersDbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<User> Users { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<Review> Reviews { get; set; }
        public DbSet<Cart> Carts { get; set; }
 
        public StoreDbContext(DbContextOptions<StoreDbContext> options) : base(options) { }
 
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }
}
планировалось в инетефейсах, реализовать методы доступа к каждому DbSet<Entity>, CRUD и всё вот это вот

просто код та рабочий, и инкапсуляция вроде соблюдается при таком подходе, а вот с тестами затык вышел...обидно
0
HF
 Аватар для HF
1303 / 882 / 199
Регистрация: 09.09.2011
Сообщений: 2,590
Записей в блоге: 2
03.04.2024, 11:46
Цитата Сообщение от paiiiko Посмотреть сообщение
то есть такая реализация не подходит?
Не могу однозначно на этот вопрос ответить.

Цитата Сообщение от paiiiko Посмотреть сообщение
планировалось в инетефейсах, реализовать методы доступа к каждому DbSet<Entity>, CRUD и всё вот это вот
просто код та рабочий, и инкапсуляция вроде соблюдается при таком подходе, а вот с тестами затык вышел...обидно
На первый взгляд нет причин считать что так сделать нельзя.

У вас же есть интерфейсы
C#
1
2
3
4
5
public class StoreDbContext : DbContext, ICartDbContext,
                                             IOrdersDbContext,
                                             IProductsDbContext,
                                             IReviewDbContext,
                                             IUsersDbContext
значит в тестах нужно мокать интерфейсы, но сетапить свои данные. Вам же не нужно реально лезть в БД, значит и зацикливаться на реальном DbContext тоже нет.
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,227
03.04.2024, 12:05
Лучший ответ Сообщение было отмечено paiiiko как решение

Решение

Цитата Сообщение от paiiiko Посмотреть сообщение
я вижу что обьекты mock создаются но метод GetAll() не может получить к ним доступ.
Это ожидаемое поведение у moq, прочитайте вот эту статейку.
Чтобы заставить это работать, надо просто установить свойство CallBase в true.
This is the expected behavior from Moq (and mocking frameworks in general). When we configure a mock object, we only need to fill in the items that we use. And that can be a big time saver, particularly if we need to quickly mock up a dependency needed for a test.

When we create an object manually (FakePolygonWithDefault), we need to provide implementations for all of the abstract members of the interface. This means that we have to provide an implementation for "GetArea" even though we do not use that method in the tests.
***Update for Moq (April 2022)***
Moq has been updated to support calling default implementation for members. In order for the default implementation to be used, set the "CallBase" property on the mock object to "true".
C#
1
mock.CallBase = true;
Миниатюры
Проблема с Mock и IDbContext в тестах  
1
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,227
03.04.2024, 13:07
Цитата Сообщение от paiiiko Посмотреть сообщение
то есть такая реализация не подходит?
Пойдет, но обычно делают обертку сверху и называют это репозиторием.

Добавлено через 54 минуты
Цитата Сообщение от IamRain Посмотреть сообщение
Чтобы заставить это работать, надо просто установить свойство CallBase в true.
Эм, немного скомкано ответил, это нужно для того чтобы можно было использовать дефолтную реализацию интерфейсов в mock-объектах.

Добавлено через 3 минуты
И правильно - исправлять именно тесты, а не переписывать логику, чтобы тесты не падали. Тесты - это второстепенное.
0
0 / 0 / 0
Регистрация: 06.07.2021
Сообщений: 58
03.04.2024, 13:32  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
И правильно - исправлять именно тесты, а не переписывать логику, чтобы тесты не падали. Тесты - это второстепенное.
Да, я понимаю. Но так я понял в чём дело.
Цитата Сообщение от IamRain Посмотреть сообщение
Эм, немного скомкано ответил, это нужно для того чтобы можно было использовать дефолтную реализацию интерфейсов в mock-объектах.
Вы своим ответом попали в яблочко. Спасибо. всё работает
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
03.04.2024, 13:32
Помогаю со студенческими работами здесь

Mock in Unittest
Доброго времени суток! Во время написания теста столкнулся с проблемой. На сильно упрощённом примере, приведённом ниже, попробую объяснить....

Mock тесты
public void GenericRepository_Add() { _mockContext.Setup(x =&gt; x.Add(_pen)); ...

Mock тесты
public async Task DeleteFromDataBase(int id) { using var httpResponseMessage = await...

No Last call on a Mock available
Столкнулся с такой проблемой при тестировании сервиса. @Test public void testExecuteCommand() { IMocksControl control =...

Mock тестирование
jmock 2.6.0 (Java 6) это последняя библиотека которую я нашел на их офф.сайте. Я использую Java 8, поэтому у меня практически весь...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
9
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru