← Назад в блог

Юнит-тестирование C# с Moq: проверка вызовов методов и исключений

Опубликовано
2 мин чтения
--- просмотров

Moq — полезный фреймворк, который позволяет симулировать любую функциональность объекта, который тестируется. Например, тебе нужно проверить, был ли вызван какой-то метод в тестовом сценарии — moq позволяет это сделать. Просто замокай тестируемый метод, запусти сценарий и в конце проверь, был ли он вызван. Официальная документация и примеры доступны здесь — Quickstart. А ещё хочу показать пару полезных для меня фич. Читай ниже. ### Проверка, был ли вызван метод внутри объекта.

Например, у тебя есть интерфейс логгера и его реализация:

using System;

namespace ...
{
    public interface IIntegrationLog
    {
        void Error(string msg);
        void ExecuteWithLogging(Action action);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
...

namespace ...
{

    public class IntegrationLog: IIntegrationLog
    {
        ...

        public virtual void Error(string msg)
        {
            Log(Error, msg);
        }

        public void ExecuteWithLogging(Action action)
        {
            try
            {
                action.Invoke();
            }
            catch (Exception ex)
            {
                this.Error(ex.Message);
            }
        }
    }
}

Основная задача — проверить, был ли вызван метод Error внутри, и если да, то сколько раз? Поэтому я написал для этого тестовый класс:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace ...
{
    [TestClass]
    public class IntegrationLogTests
    {
        private Mock<IntegrationLog> _integrationLog;

        // mocking testable object:
        // implementing method Error() taking string as argument
        [TestInitialize]
        public void TestInitialize()
        {
            this._integrationLog = new Mock<IntegrationLog>();

            this._integrationLog.Setup(m => m.Error(It.IsAny<string>()));

        }

        // caller method not twrows any exceptions
        // so, => Error not called
        [TestMethod]
        public void ExecuteWithLogging_ExecuteWithLogging_Successed()
        {
            this._integrationLog.Object.ExecuteWithLogging(() =>
            {
                return;
            });

            this._integrationLog.Verify(m => m.Error(error), Times.Never());
        }

        // caller throws 1 exception
        [TestMethod]
        public void ExecuteWithLogging_ExecuteWithLogging_ExceptionCaught()
        {
            this._integrationLog.Object.ExecuteWithLogging(() =>
            {
                throw new System.Exception(error);
            });

            this._integrationLog.Verify(m => m.Error(error), Times.Once());
        }

        // caller throws 2 exception
        [TestMethod]
        public void ExecuteWithLogging_ExecuteWithLogging_ExecuteWithLogging2Level()
        {
            this._integrationLog.Object.ExecuteWithLogging(() =>
            {
                for (int i = 0; i < 2; i++)
                {
                    this._integrationLog.Object.ExecuteWithLogging(() =>
                    {
                        throw new System.Exception(error);
                    });
                }
            });

            this._integrationLog.Verify(m => m.Error(error), Times.Exactly(2));
        }

        // caller throws 3 exception
        [TestMethod]
        public void ExecuteWithLogging_ExecuteWithLogging_ExecuteWithLogging3Level()
        {
            this._integrationLog.Object.ExecuteWithLogging(() =>
            {
                for (int i = 0; i < 2; i++)
                {
                    this._integrationLog.Object.ExecuteWithLogging(() =>
                    {
                        throw new System.Exception(error);
                    });
                }

                throw new System.Exception(error);
            });

            this._integrationLog.Verify(m => m.Error(error), Times.Exactly(3));
        }
    }
}

Открыт для работы по контракту

Я доступен для работы по контракту. Если у вас есть интересная идея проекта — запишитесь на звонок через Calendly.

Записаться на 30-минутный звонок