4

I'm in the process of writing unit tests against our core framework, and came across this.

We have an extension method that looks like this:

    public static T ThrowIfDefault<T>(this T self, string variableName)
    {
        if (self.Equals(default(T)))
            throw new ArgumentException(string.Format("'{0}' cannot be default(T)", variableName));
        return self;
    }   // eo ThrowIfDefault<T>

(A variation on a ThrowIfNull<> extension method I saw here on Stack Overflow.

In writing a test case for this, I first wrote a helper:

    public void ThrowIfDefaultTestHelper<T>(T value)
    {
        // unit test *itself* requires that a value be specified!!
        Assert.AreNotEqual(default(T), value);

        // Good test
        GenericExtensionMethods.ThrowIfDefault(value, "value");

        // Bad test
        try
        {
            GenericExtensionMethods.ThrowIfDefault(default(T), "value");
        }
        catch (ArgumentException)
        {
            // Expected result
        }
        catch (Exception)
        {
            throw;
        }
    }

And then the following:

    [TestMethod()]
    public void ThrowIfDefaultTest()
    {
        ThrowIfDefaultTestHelper<int>(10);
        ThrowIfDefaultTestHelper<Guid>(Guid.NewGuid());
        ThrowIfDefaultTestHelper<DateTime>(DateTime.Now);
        ThrowIfDefaultTestHelper<object>(new { Name = "Test" });    // anonymous object
    }

The Unit test fails on the last one as a NullReferenceException is thrown, because I am guessing object has no default(T) (or does it?). Can I not test anonymous objects this way?

Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • 1
    It's kind of strange to use an instance method (`Equals`) in a method whose only purpose is to check for `null`... – Daniel Hilgarth Apr 23 '13 at 18:51
  • 1
    @DanielHilgarth, absolutely! It's the little things that people miss that when others look at, it's utterly obvious. Hence, very good idea to use unit tests... we're all human :) – Moo-Juice Apr 23 '13 at 18:54

1 Answers1

11

object does have default(T), it just happens to be null. This is unlike your other test cases that use non-nullable value types. That's why you get a NullReferenceException instead of the one that you expect.

If you replace

self.Equals(default(T))

with

EqualityComparer<T>.Default.Equals(obj, default(T))

you should start getting the expected ArgumentException.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • *slaps forehead* That makes perfect sense. Thank you. – Moo-Juice Apr 23 '13 at 18:50
  • 1
    Furthermore, this is a perfect example of why unit-testing is important on apparent "that'll always work" code :) – Moo-Juice Apr 23 '13 at 18:52
  • One last point, just for clarity - I do pass `string` in there which is not a value type, and the test passes fine. I am assuming therefore `default(T)` for string is non-null? – Moo-Juice Apr 23 '13 at 18:57
  • 1
    @Moo-Juice: In the code you provided, you don't pass `default(T)` with `T == typeof(string)`. If you would do that, you would get the `NullReferenceException` just as you do for every other reference type. `default(string)` is `null`. – Daniel Hilgarth Apr 23 '13 at 18:59
  • @DanielHilgarth, good point - so it's safe to say it would have died there instead of the last one. My bad. Thanks again. – Moo-Juice Apr 23 '13 at 18:59
  • @Moo-Juice One more thing, the code that I wrote for comparing with `null` is not as good when you use value types. I edited the answer with a portion of Marc's [answer](http://stackoverflow.com/a/864860/335858) that I used throughout my code. – Sergey Kalinichenko Apr 23 '13 at 19:17
  • @dasblinkenlight, thanks! I have updated my function, and as expected - the unit test passes with flying colours and dancing girls. – Moo-Juice Apr 23 '13 at 19:23