Як працює мок даних в .NET — розбираємо на прикладі з кодом

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Всім привіт! На моєму поточному проєкті я, досить часто, пишу юніт-тести, і майже в кожному з них ми використовуємо ту чи іншу бібліотеку для створення моків.

Одного разу мені стало цікаво, як відбувається так, що навіть якщо в нас немає конкретної імплементації якогось інтерфейсу (зовсім!), ми все одно маємо змогу зробити повернення результату таким, який ми самі бажаємо. Тобто «замокати» функцію.

Мені стало цікаво дізнатись, як це відбувається і спробувати написати невелику бібліотеку для мокання функцій, тож у цій статті про це і поговоримо.

Розглянемо вже існуючі рішення

Спочатку я вирішив розглянути два найпопулярніших фреймворка для мокання даних — NSubstitute і Moq. Вони досить схожі між собою.

Посилання на залежності в NSubstitute

Посилання на залежності в Moq

Легко можна помітити, що обидва фреймворки використовують одну й ту саму бібліотеку — Castle.Core. (навіть версія однакова🙂)

Ознайомившись детальніше, можна побачити, що в пакеті використовується Castle.DynamicProxy. Тут є посилання на GitHub.

Трохи погортавши документацію, було виявлено, що ця магічна асемблі дозволяє за допомогою реалізації інтерцепторів, вторгатися в роботу метода, і перехоплювати роботу оригінального метода, з поверненням потрібного нам результату (звісно, що це не єдині можливості бібліотеки, але мене цікавить саме це).

Перейдемо до написання коду

Перш за все, напишемо метод аналог Returns в NSubstitute — ConfigureMethod.

Простими словами, ми загортаємо делегат Func у Expression, щоб мати змогу аналізувати та маніпулювати кодом усередині лямбда-виразу під час виконання.

Таким чином, ми можемо перевіряти та витягувати таку інформацію, як назва методу, типи аргументів і тіло лямбда-виразу.

Після того, як ми отримали усю корисну інформацію про метод (а саме назву та параметри), ми записуємо в словник цю інформацію і додаємо значення, яке ми б хотіли повернути при виклику даного метода. Власне, тут ми і робимо мок методу.

В аналогічний словник запишемо, що метод ще ні разу не викликався (кількість викликів — 0).

Напишемо інтерцептор, який буде перехоплювати роботу метода і робити потрібні нам речі зі збереження інформації:

Для цього ми імплементуємо інтерфейс з бібліотеки Castle.DynamicProxy. В конструкторі передамо посилання на словники зі збереженою інформацією. В даному методі, параметр invocation інкапсулює виклик проксі-метода.

Потім перевіримо, чи є в нас інформація про цей метод (ми мали дізнатися цю інформацію під час виклику ConfiguteMethod), і якщо є, то маємо встановити значення для повернення, взявши його із заздалегідь записаного.

Також збільшимо на 1 кількість викликів цього методу.

Перейдемо до конструктора нашого мока:

Тут ми створюємо наступні пункти:

generator — інстанс генератора проксі, наданого DynamicProxy;

mockInterceptor — це інстанс класу інтерцептора, який ми створювали до цього. Інтерцептор відповідає за перехоплення викликів методів на проксі-об’єкті і виконання додаткових дій або зміни поведінки;

Щодо CreateInterfactProxyWithoutTarget — даний метод створює проксі-об’єкт, що перехоплює виклики до класів інтерфейсу TInterface.
Проксі-об’єкт генерується під час рантайму за допомогою mockInterceptor.

Перейдемо до написання юніт-тестів з використанням щойно створеної бібліотеки!

Власне, маємо синтаксис, який є інтуїтивно зрозумілим і легким, і повне проходження тесту, з перевіркою роботи бібліотеки.

Таким чином, принцип роботи зводиться до збереження значення, яке ми хочемо замокати і перехоплення роботи метода за допомогою Castle.Core.
Потім, замість виклику оригінального метода, ми повертаємо збережене значення.
DynamicProxy дає змогу для реалізації безлічі речей, наприклад, можна логувати виклик метода, чи доповнити функціонал старої та непітримуваної більше бібліотеки.

Звісно, це є лише маленьким прототипом і не претендує на використання, але після написання цього проєкту велика таємниця написання моків зникла.

👍ПодобаєтьсяСподобалось10
До обраногоВ обраному2
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Два тижня тому робив стрім де розбирав як раз цю тему:
youtube.com/...​rLpbrQrm-mw?feature=share

хех, ніколи не задумувався, що відбувається під капотом у Moq, дякую за огляд

Підписатись на коментарі