Как использовать Moq для упрощения модульного тестирования на C #

Нам часто нужно писать модульные тесты для кода, который обращается к внешнему ресурсу, например к базе данных или файловой файловой системе. Если такие ресурсы недоступны, единственный способ убедиться, что тесты могут выполняться, - это создать фиктивные объекты. По сути, используя поддельные реализации этих базовых зависимостей, вы можете проверить взаимодействие между тестируемым методом и его зависимостями. Три самых популярных фреймворка для создания макетов для разработчиков .Net - это Rhino Mocks, Moq и NMock.

Среди них Moq может быть наиболее гибким и простым в использовании. Фреймворк Moq предоставляет элегантный способ настройки, тестирования и проверки макетов. В этой статье рассматривается Moq и то, как его можно использовать для изоляции модулей кода от их зависимостей.

Начало работы с Moq

Вы можете использовать Moq для создания фиктивных объектов, имитирующих или имитирующих реальный объект. Moq можно использовать для имитации классов и интерфейсов. Однако есть несколько ограничений, о которых вам следует знать. Имитируемые классы не могут быть статическими или запечатанными, а макетируемый метод должен быть помечен как виртуальный. (Обратите внимание, что есть обходные пути для этих ограничений. Вы можете имитировать статический метод, например, воспользовавшись шаблоном проектирования адаптера.)

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

Install-Package Moq

Как имитировать интерфейсы с помощью Moq

Начнем с макета интерфейса. Синтаксис для создания фиктивного объекта с использованием класса Mock приведен ниже.

Mock mockObjectType = новый Mock ();

Теперь рассмотрим следующий интерфейс с именем IAuthor.

публичный интерфейс IAuthor

    {

        int Id {получить; набор; }

        строка FirstName {получить; набор; }

        строка LastName {получить; набор; }

    }

Используя структуру Moq, вы можете создать фиктивный объект, установить значения свойств, указать параметры и вернуть значения при вызовах методов. В следующем фрагменте кода показано, как создать экземпляр из интерфейса IAuthor с помощью Moq.

var mock = новый Mock ();

Обратите внимание, что класс Mock принадлежит структуре Moq и содержит универсальный конструктор, который принимает тип интерфейса, который вы хотите создать. Moq использует лямбда-выражения, делегаты и дженерики. Все это делает использование фреймворка очень интуитивным.

В следующем фрагменте кода показано, как можно имитировать интерфейс IAuthor и предоставить свойства фиктивного экземпляра с соответствующими значениями. Обратите внимание, как мы используем Assert для проверки значений свойств имитируемого экземпляра.

var author = new Mock ();

author.SetupGet (p => p.Id) .Returns (1);

author.SetupGet (p => p.FirstName) .Returns («Joydip»);

author.SetupGet (p => p.LastName) .Returns («Канжилал»);

Assert.AreEqual («Joydip», автор.Object.FirstName);

Assert.AreEqual («Канджилал», author.Object.LastName);

Как имитировать методы с помощью Moq

Теперь рассмотрим следующий класс с именем Article. Класс Article содержит только один метод с именем GetPublicationDate, который принимает идентификатор статьи в качестве параметра и возвращает дату публикации статьи.

статья публичного класса

    {

        общедоступный виртуальный DateTime GetPublicationDate (int articleId)

        {

            выбросить новое NotImplementedException ();

        }

    }

Поскольку метод GetPublicationDate еще не реализован в классе Article, метод был смоделирован для возврата текущей даты в качестве даты публикации, как показано в приведенном ниже фрагменте кода.

var mockObj = новый Mock ();
mockObj.Setup (x => x.GetPublicationDate (It.IsAny ())). Returns ((int x) => DateTime.Now);

Метод Setup используется для определения поведения метода, который передается ему в качестве параметра. В этом примере он используется для определения поведения метода GetPublicationDate. Вызов It.IsAny()подразумевает, что метод GetPublicationDate примет параметр целочисленного типа; Itотносится к статическому классу. Метод Returns используется для указания возвращаемого значения метода, указанного в вызове метода Setup. В этом примере метод Returns используется для указания возвращаемого значения метода в качестве текущей системной даты.

Moq позволяет проверить, был ли вызван конкретный метод или свойство. Следующий фрагмент кода иллюстрирует это.

mockObj.Verify (t => t.GetPublicationDate (It.IsAny ()));

Здесь мы используем метод Verify, чтобы определить, был ли вызван GetPublicationDate для фиктивного объекта.

Как имитировать методы базового класса с помощью Moq

Рассмотрим следующий фрагмент кода. Здесь у нас есть два класса - класс RepositoryBase и класс AuthorRepository, который его расширяет.

открытый абстрактный класс RepositoryBase

{

    общедоступный виртуальный логический объект IsServiceConnectionValid ()

    {

        // Какой-то код

    }

}

открытый класс AuthorRepository: RepositoryBase

{

    public void Сохранить ()

    {

        если (IsServiceConnectionValid ())

        {

            // Какой-то код

        }

    }

}

Теперь предположим, что мы хотим проверить, действительно ли соединение с базой данных. Однако мы можем не захотеть тестировать весь код внутри метода IsServiceConnectionValid. Например, метод IsServiceConnectionValid может содержать код, относящийся к сторонней библиотеке. Мы бы не хотели это проверять, правда? Здесь на помощь приходит метод CallBase в Moq. 

В подобных ситуациях, когда у вас есть метод в базовом классе, который был переопределен в имитируемом типе, и вам нужно имитировать только базовую версию переопределенного метода, вы можете использовать CallBase. В следующем фрагменте кода показано, как можно создать частичный фиктивный объект класса AuthorRepository, задав для свойства CallBase значение true.

var mockObj = new Mock () {CallBase = true};

mockObj.Setup (x => x.IsServiceConnectionValid ()). Returns (true);

Инфраструктура Moq позволяет легко создавать фиктивные объекты, имитирующие поведение классов и интерфейсов для тестирования, с использованием только необходимых вам функций. Чтобы узнать больше о тестировании с помощью моков, прочтите эту замечательную статью Мартина Фаулера.