Ловим исключения в MSTest: полный гайд по Assert.ThrowsException

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как поймать исключение в MSTest и не сойти с ума?

Вы пишете юнит-тесты на C# и вдруг понимаете: ваш метод должен выбросить исключение в определённой ситуации. Как это проверить? Если вы гуглили «How do I use Assert to verify that an exception has been thrown with MSTest», то вы не одиноки — это один из самых популярных вопросов на Stack Overflow. И сегодня мы разберём его раз и навсегда.

В этой статье я покажу вам все способы проверки исключений в MSTest: от классического атрибута до современного Assert.ThrowsException. Вы узнаете, как писать чистые, надёжные тесты, которые не упадут в самый неподходящий момент. Поехали!

Почему проверка исключений — это важно?

Представьте: у вас есть метод Divide(int a, int b). Если b = 0, он должен выбросить DivideByZeroException. Без теста вы узнаете об этом только когда приложение упадёт в production. Unit-тест с проверкой исключения — это ваш страховочный трос.

В MSTest (фреймворк для модульного тестирования от Microsoft) есть несколько подходов. Мы рассмотрим каждый, но начнём с самого современного и рекомендуемого.

Метод 1: Assert.ThrowsException (Самый правильный)

Начиная с MSTest V2 (версия 2.x), у нас есть встроенный метод Assert.ThrowsException<T>. Это элегантно, читаемо и гибко. Синтаксис простой:

[TestMethod]
public void Divide_ByZero_ThrowsDivideByZeroException()
{
    // Arrange
    var calculator = new Calculator();

    // Act & Assert
    Assert.ThrowsException<DivideByZeroException>(() => calculator.Divide(10, 0));
}

Что здесь происходит? Мы передаём лямбда-выражение с вызовом метода. MSTest выполняет его внутри блока try-catch. Если исключение НЕ выбрасывается — тест падает. Если выбрасывается исключение другого типа — тоже падает. Только нужное исключение приводит к успеху.

Продвинутый пример: проверка сообщения исключения

Иногда нужно убедиться не только в типе исключения, но и в его сообщении. Для этого используйте перегрузку с параметром message или проверяйте свойства объекта исключения:

[TestMethod]
public void GetUserById_InvalidId_ThrowsWithCorrectMessage()
{
    // Arrange
    var service = new UserService();

    // Act
    var exception = Assert.ThrowsException<ArgumentException>(() => service.GetUserById(-1));

    // Assert дополнительно
    Assert.AreEqual("Id must be positive", exception.Message);
}

Обратите внимание: Assert.ThrowsException возвращает объект исключения! Это позволяет вам проверить любые его свойства: InnerException, Data, StackTrace (хотя последний обычно не проверяют).

Метод 2: Атрибут [ExpectedException] (Устаревший, но всё ещё в ходу)

Раньше (в MSTest V1) использовался атрибут над самим методом теста. Выглядело это так:

[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void Divide_ByZero_ThrowsDivideByZeroException_OldWay()
{
    var calculator = new Calculator();
    calculator.Divide(10, 0);
}

Кажется удобно? Но есть три проблемы:

  • Нельзя проверить сообщение исключения. Вы не знаете, то ли исключение выброшено.
  • Любое исключение этого типа — success. Даже если оно вылетело из другой строки кода (например, в Arrange).
  • Нельзя выполнить код после исключения. Метод теста прерывается сразу.

Поэтому в новых проектах используйте Assert.ThrowsException. Атрибут [ExpectedException] помечен как deprecated (устаревший) и может быть удалён в будущих версиях.

Метод 3: Ручной try-catch (Для экстремалов)

Иногда (очень редко) вам нужен полный контроль. Например, если вы тестируете асинхронный код или хотите проверить, что исключение выбрасывается в середине сложного метода. Тогда можно сделать так:

[TestMethod]
public void ProcessData_InvalidInput_ThrowsException_Manual()
{
    // Arrange
    var processor = new DataProcessor();

    // Act
    bool exceptionThrown = false;
    try
    {
        processor.ProcessData(null);
    }
    catch (InvalidOperationException)
    {
        exceptionThrown = true;
    }

    // Assert
    Assert.IsTrue(exceptionThrown, "Expected InvalidOperationException was not thrown.");
}

Этот метод даёт 100% контроль, но требует больше кода. Используйте его только когда Assert.ThrowsException не подходит (например, при тестировании многопоточности).

Асинхронные исключения: Assert.ThrowsExceptionAsync

Современный код полон асинхронных методов. Для них есть специальная версия:

[TestMethod]
public async Task FetchDataAsync_NetworkError_ThrowsHttpRequestException()
{
    // Arrange
    var client = new HttpClientWrapper();

    // Act & Assert
    await Assert.ThrowsExceptionAsync<HttpRequestException>(async () => 
        await client.FetchDataAsync("http://invalid.url"));
}

Важно: метод теста должен быть async Task, а не async void. И не забудьте про await перед Assert.ThrowsExceptionAsync — иначе тест завершится до того, как исключение будет поймано.

Частые ошибки и как их избежать

Даже опытные разработчики иногда спотыкаются. Вот топ-3 ошибки при проверке исключений в MSTest:

Ошибка 1: Забыли про лямбду

Не делайте так:

// ❌ Неправильно!
Assert.ThrowsException<Exception>(calculator.Divide(10, 0));

Вы передаёте результат вызова метода, а не делегат. Исключение вылетит до того, как Assert начнёт работать. Правильно:

// ✅ Правильно!
Assert.ThrowsException<Exception>(() => calculator.Divide(10, 0));

Ошибка 2: Слишком общее исключение

Не ловите Exception без необходимости. Тест пройдёт, если метод выбросит NullReferenceException вместо ожидаемого ArgumentException. Это скроет баг. Всегда указывайте максимально конкретный тип.

Ошибка 3: Не проверяете состояние после исключения

Иногда нужно убедиться, что после исключения объект остался в консистентном состоянии. Assert.ThrowsException позволяет это сделать:

[TestMethod]
public void Withdraw_InsufficientFunds_ThrowsAndBalanceUnchanged()
{
    // Arrange
    var account = new BankAccount(100);

    // Act
    Assert.ThrowsException<InsufficientFundsException>(() => account.Withdraw(200));

    // Assert                

Блоги

Рекомендаций по книгам