<CharlieDigital/> Programming, Politics, and uhh…pineapples


MbUnit CsvDataAttribute

MbUnit has several cool features which distinguish it from some of the other unit testing frameworks on the .NET platform. Among them are the RollbackAttribute, PrincipalAttribute, ThreadedRepeatAttribute, and the Csv/XmlDataAttribute.

I hadn't noticed the CsvDataAttribute previously when I've worked with MbUnit, but it's definitely one that I think that most teams can make the most use of. While the RowAttribute allows developers to externalize and parameterize their unit tests, the CsvDataAttribute takes it to another level by allowing developers to put test parameters in a simple text file. This is extremely handy since it becomes easier to add more test conditions as you come up with new scenarios without recompiling code. Theoretically, you could even involve your QA team in getting the right set of test data since they could modify the external CSV file. I find this extremely handy 🙂

The documentation on how to use it was lacking a bit; while it explained that you can add metadata (custom attributes) via the CSV file, it didn't give an example for the ExpectedExceptionAttribute, one of the most common ones, I'd imagine.

Consider the following property which normalizes and validates a phone number (note: this was meant as a simple example):

/// <summary>
/// Gets or sets the number.
/// </summary>
/// <value>The number.</value>
public string Number
    get { return _number; }
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException(
                "The phone number cannot be null or empty.");

        // Grab all the digits.
        char[] digits = value.ToCharArray()
            .Where(c => char.IsDigit(c)).ToArray();

        if (digits.Length != 10)
            throw new FormatException(
                "A phone number must contain 10 digits.");

        _number = new string(digits);

The test method might look like this:

[CsvData(FilePath = "CsvData\\PhoneNumbers.txt", HasHeader = true)]
public void TestPhoneNumberNormalizationWithCsv(
    string type, string number, string expected)
    PhoneNumber phoneNumber = new PhoneNumber(0, 0, number, type);

    Assert.AreEqual(expected, phoneNumber.Number);

Now we'd like to test our validation logic to gaurd against future refactorings to make sure that anyone refactoring this code throws the appropriate exceptions that our downstream callers expect.

You can see that I've used the FilePath property and the HasHeader property (you have to use this if there is a header, otherwise, it detects the header as a row; it's not true by default it seems). The text file to go with this test would then look like:

Type, Number, Expected, [ExpectedException]
Home, (732) 555-1012 begin_of_the_skype_highlighting              (732) 555-1012      end_of_the_skype_highlighting, 7325551012
Home, , , ArgumentException

There are a few things to note here:

  1. If no exceptions are associated with the row, don't include a trailing comma and empty value (see the first line).
  2. The headers are not case sensitive.
  3. Null values can be specified using an empty value.
  4. When specifying exceptions, you do not need to use typeof(ArgumentException), just the type is enough.

Happy (unit) testing!

Posted by Charles Chen

Filed under: .Net, Dev Comments Off
Comments (0) Trackbacks (0)

Sorry, the comment form is closed at this time.

Trackbacks are disabled.