Предыдущий ролик Следующий ролик  

Видео урок: Mocking dependencies

Основы ASP.NET MVC 5

В этом видео я познакомлю вас с концепцией имитации и продемонстрирую возможный вариант создания простого DbContext, благодаря которому можно будет быстро писать и выполнять тесты, используя находящиеся в памяти тестовые данные, которые придумываются на лету. Мы рассмотрим процесс тестирования метода Deposit контроллера Transaction. Я не хочу, чтобы во время написания теста мне приходилось думать о том, что же сейчас находится в нашей базе данных и какой CheckingAccountId мне использовать. Было бы замечательно, если бы я мог протестировать этот код, состряпав некоторые вымышленные данные с помощью находящихся в памяти объектов.

Таким образом, тест будет работать независимо от среды и базы данных. Для этого я несколько поменяю представление контроллера о том, какой элемент базы данных мы здесь определили. Я укажу, что db будет не экземпляром ApplicationDbContext, а экземпляром некоего класса, который реализует интерфейс под названием IApplicationDbContext. Он может подключаться и к реальным данным базы данных, и к вымышленным, находящимся в памяти данным. Через некоторое время мы определим этот интерфейс.

Я не буду задавать его прямо здесь, потому что собираюсь написать два конструктора, которые будут управлять этим назначением. Один - для обычного использования нашим приложением, а второй - для использования в целях тестирования. Теперь, когда у нас все настроено, мы можем создать экземпляр TransactionController и передать в него mock-версию DbContext с помощью этого конструктора. Итак, нам нужно решить две проблемы. Во-первых, нужно определить наш интерфейс, а во-вторых, нужно убедиться, что существующий ApplicationDbContext реализует этот интерфейс. Для начала нужно переместить ApplicationDbContext, расположенный в файле IdentityModels.cs, в наш собственный файл, потому что на самом деле больше нет никакого смысла хранить его там из-за того, что он связан с сущностями, а также с текущими счетами и операциями. Поэтому я создам новый класс ApplicationDbContext и вставлю внутрь него этот код.

Нам нужно разрешить еще несколько пространств имен. А затем я определю в этом файле еще и интерфейс. Как и в случае с реальным DbContext, который реализует этот интерфейс, нам потребуются элементы с названиями "CheckingAccounts" и "Transactions". Тем не менее если определить их как DbSet, то мы столкнемся с аналогичной проблемой привязки к базе данных, в то время, как на самом деле нам не хочется к ней подключаться. Если вы более подробно проанализируете определения, то обнаружите, что DbSet уже реализует интерфейс под названием IDbSet, который можно использовать взамен нужного нам интерфейса, чтобы определить эти элементы, не фиксируя изменения в базе данных.

Теперь можно выйти из этого класса, добавив метод SaveChanges, потому что в нашем коде мы больше ничего не вызываем для объекта db напрямую. Наконец, нам хочется, чтобы вот эти элементы тоже относились к типу IDbSet. Еще у нас должна быть возможность указать, что ApplicationDbContext реализует интерфейс IApplicationDbContext. Я хочу, чтобы этот интерфейс располагался в данном файле и в данном пространстве имен, но не в данном классе, поэтому я перенесу его определение вверх.

Опять же, нужно указать, что ApplicationDbContext реализует интерфейс IApplicationDbContext. Давайте создадим реализацию вымышленного DbContext. Она будет похожа на наш интерфейс, за исключением того, что нам придется реализовать метод SaveChanges, поскольку этот DbContext используется в нашем коде. Он также будет реализовывать интерфейс IApplicationDbContext. Скопируем эти члены и реализуем метод SaveChanges. Мы реализуем эти вымышленные DbSet в виде списков.

Таким образом, если мы будем использовать, например, метод Add для CheckingAccounts, то этого будет достаточно для имитации создания нового текущего счета. Поэтому можно просто вернуть int и сохранить изменения, не выполняя никаких реальных действий. Последнее, что нам понадобится, - вымышленный DbSet, который работает с расположенным в памяти списком, а не с реальной базой данных. Чтобы корректно реализовать IDbSet, необходимо определить множество различных членов. Множество реализаций IDbSet можно встретить в разных онлайн-блогах и репозиториях.

Здесь вы видите одну из таких реализаций, используемую на GitHub Gist и предназначенную для хранения и совместного использования небольших фрагментов кода. Итак, я нажму кнопку Raw, а затем скопирую весь этот текст и добавлю новый класс под названием DbSet. Но при этом буду использовать не это пространство имен, а то, которое я использовал для своих моделей - AutomatedTellerMachine.Models. Обратите внимание, что в таких методах, как Add и Remove, используется только список - общий список, который создается при создании экземпляра.

Теперь, когда мы выполнили всю эту работу, можно, наконец, приступить к написанию нескольких тестов, используя наш вымышленный DbContext. Предположим, нам хочется написать более полезный тест, проверяющий корректность баланса счета после внесения на него средств. В качестве шага подготовки можно создать вымышленный DbContext и инициализировать свойство CheckingAccounts как FakeDbSet. Затем можно создать новый checkingAccount и присвоить ему любой идентификатор и номер счета.

После этого я добавлю его в CheckingAccounts. Очевидно, что этот тест еще не закончен. На самом деле мне придется еще выполнить метод контроллера и посмотреть, что произойдет с балансом. Но пока вы посмотрели, как можно имитировать такую зависимость, как база данных, для того чтобы тестировать свой код. В следующем видео во время обсуждения разработки через тестирование мы завершим этот тест и добавим в наше приложение некоторую новую логику, чтобы убедиться, что данный тест выполняется успешно.