39

In my test program in Nunit, I want to verify that it's getting the write Argument Exception by verifying the message.

    [Test]
    public void ArgumentsWorkbookNameException()
    {
        const string workbookName = "Tester.xls";
        var args = new[] { workbookName, "Sheet1", "Source3.csv", "Sheet2", "Source4.csv" };
        Assert.Throws(typeof(ArgumentException), delegate { var appargs = new ApplicationArguments(args); }, "Invalid ending parameter of the workbook. Please use .xlsx");

    }

After testing this out, this doesn't work when I modified the message in the main program.

        int wbLength = args[0].Length;

        // Telling the user to type in the correct workbook name file.
        if (args[0].Substring(wbLength-5,5)!=".xlsx")
        {
            throw new ArgumentException(
                "Invalid ending parameter of the workbook. Please use .xlsx random random");
        }

The unit test still passed, regardless if I changed the message.

How do I do it? Or is there no such things in C#. My colleague said there are options like that in Ruby and RSPEC, but he's not 100% sure on C#.

Henry Zhang
  • 391
  • 1
  • 3
  • 3

4 Answers4

65

Use the fluent interface to create assertions:

Assert.That(() => new ApplicationArguments(args), 
    Throws.TypeOf<ArgumentException>()
        .With.Message.EqualTo("Invalid ending parameter of the workbook. Please use .xlsx random random"));
PleasantD
  • 722
  • 5
  • 8
  • Fluent assertions appears to be an open source add-on for NUnit, with a summary located at https://lukewickstead.wordpress.com/2013/02/09/howto-nunit-fluent-assertions/ And source located at https://github.com/mvbalaw/FluentAssert – baker.nole Mar 09 '18 at 20:22
34

I agree with Jon that "such tests are unnecessarily brittle". However, there are at least two ways to check for exception message:

1: Assert.Throws returns an exception, so you can make an assertion for its message:

var exception = Assert.Throws<ArgumentException>(() => new ApplicationArguments(args));
Assert.AreEqual("Invalid ending parameter of the workbook. Please use .xlsx random random", exception.Message);

2: [HISTORICAL] Before NUnit 3, you could also use ExpectedException attribute. But, take a note that attribute waits for an exception in the whole tested code, not only in code which throws an exception you except. Thus, using this attribute is not recommended.

[Test]
[ExpectedException(typeof(ArgumentException), ExpectedMessage = "Invalid ending parameter of the workbook. Please use .xlsx random random")]
public void ArgumentsWorkbookNameException()
{
    const string workbookName = "Tester.xls";
    var args = new[] { workbookName, "Sheet1", "Source3.csv", "Sheet2", "Source4.csv" };
    new ApplicationArguments(args);
}

  1. You may also use FluentAssertions to do so, e.g.
subject.Invoking(y => y.Foo("Hello"))
       .Should().Throw<InvalidOperationException>()
       .WithMessage("Hello is not allowed at this moment");

Dariusz Woźniak
  • 9,640
  • 6
  • 60
  • 73
  • 4
    I would personally avoid `ExpectedExceptionAttribute`. `Assert.Throws` is clearer about where *exactly* the error is meant to occur. If the OP *really* wants to test the exception message, they'd be better off writing their own `MoreAssertions.Throws` or whatever. – Jon Skeet Jul 23 '13 at 17:05
  • @JonSkeet: Indeed, I've made clarification about that attribute. – Dariusz Woźniak Jul 23 '13 at 17:24
5

The message parameter in Assert.Throws isn't the expected exception message; it's the error message to include with the assertion failure if the test fails.

I don't believe that NUnit supports testing the exception message out of the box, and I'd argue that such tests are unnecessarily brittle anyway. If you really want to write your own such helper method you can do so, but I personally wouldn't encourage it. (I very rarely specify a test failure message either, unless it's to include some diagnostic information. If a test fails I'm going to look at the test anyway, so the message doesn't add much.)

I would encourage you to use the generic overload instead though, and a lambda expression, for simplicity:

Assert.Throws<ArgumentException>(() => new ApplicationArguments(args));

(If that's your actual code by the way, there are other problems - try passing in new[] { "xyz" } as an argument...)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 3
    but is it not worth to validate, checking the message, that the exception you are testing is the one you want and not other in a different point of your code? If same exception type but from different place maybe your code does something you don't want it to do. – Ricardo stands with Ukraine Apr 10 '14 at 06:34
  • 1
    @Riga: You can if you want to - but I don't find it to be worth my time. I like the addage of "test until fear turns to boredom". I would be very bored by testing for the *exact* exception message, and then changing the tests any time I wanted to make that exception very slightly more informative etc. So while I don't find it to be useful, your mileage may vary. – Jon Skeet Apr 10 '14 at 07:10
  • 2
    An alternative is to store your constants in a common location or class, and have both your production and test code point to the same place. If the message ever needs to change, just change it in that one location. – neverendingqs Jan 14 '16 at 22:19
  • 1
    @neverendingqs: That's certainly an option, but in most cases I'd say that does more harm than good - unless you're going to localize the message (or use the same one in multiple places) having it specified directly in the code makes it easier to read, IMO. – Jon Skeet Jan 14 '16 at 22:36
  • I agree for unit tests, the type of the exception is good enough. I'm not sure this answer is applicable for integration test scenarios though. With integration test scenarios with a large number of sites from which exceptions can be thrown, you want to make sure your getting to the layer you expect and getting the exact exception you're looking for. – Sam Shiles Aug 15 '18 at 13:36
1

In .NET Core 3.1 MSTest project, this is how I did it.

[TestMethod]
public async Task SaveItemAsync_NameIsNull_ThrowsException()
{
    var item = new Item
    {
        Name = null
    };

    var result = await Assert.ThrowsExceptionAsync<ArgumentException>(() => _service.SaveItemAsync(item));
    Assert.AreEqual("The item's name must be set.", result.Message);
}
ScubaSteve
  • 7,724
  • 8
  • 52
  • 65