How to catch an exception in MSTest without losing your mind?
\n\nYou are writing unit tests in C# and suddenly realize: your method should throw an exception in a certain situation. How do you check this? If you've Googled "How do I use Assert to verify that an exception has been thrown with MSTest", you are not alone — this is one of the most popular questions on Stack Overflow. And today we will settle it once and for all.
\n\nIn this article, I will show you all the ways to check exceptions in MSTest: from the classic attribute to the modern Assert.ThrowsException. You will learn how to write clean, reliable tests that won't fail at the worst possible moment. Let's go!
Why is checking exceptions important?
\n\nImagine: you have a method Divide(int a, int b). If b = 0, it should throw a DivideByZeroException. Without a test, you will only find out about this when the application crashes in production. A unit test with exception checking is your safety net.
In MSTest (Microsoft's unit testing framework), there are several approaches. We will look at each one, but let's start with the most modern and recommended one.
\n\nMethod 1: Assert.ThrowsException (The Most Correct)
\n\nStarting with MSTest V2 (version 2.x), we have the built-in method Assert.ThrowsException<T>. It is elegant, readable, and flexible. The syntax is simple:
[TestMethod]\npublic void Divide_ByZero_ThrowsDivideByZeroException()\n{\n // Arrange\n var calculator = new Calculator();\n\n // Act & Assert\n Assert.ThrowsException<DivideByZeroException>(() => calculator.Divide(10, 0));\n}\n\nWhat's happening here? We pass a lambda expression with the method call. MSTest executes it inside a try-catch block. If the exception is NOT thrown — the test fails. If an exception of a different type is thrown — it also fails. Only the expected exception leads to success.
\n\nAdvanced example: checking the exception message
\n\nSometimes you need to verify not only the type of exception but also its message. To do this, use the overload with the message parameter or check the properties of the exception object:
[TestMethod]\npublic void GetUserById_InvalidId_ThrowsWithCorrectMessage()\n{\n // Arrange\n var service = new UserService();\n\n // Act\n var exception = Assert.ThrowsException<ArgumentException>(() => service.GetUserById(-1));\n\n // Additional Assert\n Assert.AreEqual("Id must be positive", exception.Message);\n}\n\nNote: Assert.ThrowsException returns the exception object! This allows you to check any of its properties: InnerException, Data, StackTrace (though the latter is usually not checked).
Method 2: The [ExpectedException] Attribute (Deprecated, but still in use)
\n\nPreviously (in MSTest V1), an attribute was used on the test method itself. It looked like this:
\n\n[TestMethod]\n[ExpectedException(typeof(DivideByZeroException))]\npublic void Divide_ByZero_ThrowsDivideByZeroException_OldWay()\n{\n var calculator = new Calculator();\n calculator.Divide(10, 0);\n}\n\nSeems convenient? But there are three problems:
\n- \n
- You cannot check the exception message. You don't know if it's the right exception being thrown. \n
- Any exception of this type is a success. Even if it comes from a different line of code (e.g., in Arrange). \n
- You cannot execute code after the exception. The test method is interrupted immediately. \n
Therefore, in new projects, use Assert.ThrowsException. The [ExpectedException] attribute is marked as deprecated and may be removed in future versions.
Method 3: Manual try-catch (For the adventurous)
\n\nSometimes (very rarely) you need