I was writing a test case today and when doing the first step – “make a failing test” – I was having problem… the test kept passing even though I knew it shouldn’t. Eventually I figured out the reason is was passing is because I didn’t understand that an Enum will default to 0 if is actually Nothing.
I was testing a factory that had the very simple job of producing a logger depending on the type of logger that was requested. Here’s the factory code:
Public Shared Function MakeLogger(ByVal logmode As LoggingMode) As I_CustomErrorLogger
If logmode = LoggingMode.DATABASE Then
Return New CustomLoggerDBEnabled()
ElseIf logmode = LoggingMode.DEVNULL Then
Return New CustomLoggerDevNULL()
ElseIf logmode = LoggingMode.STANDARD_IO Then
Return New CustomLoggerStandardIO()
Return New CustomLoggerStandardIO() ' Assume old way if not told
And, as you can see, it takes an object “LoggingMode” which is an enum.
My thought was, for my FAILING test I’ll pass Nothing (this is the vb equivalent of null) as the parameter and I can then check to make sure that the type returned is something OTHER than StandardIO (because the default would be StandardIO). Remember I’m trying to make a failing test so I asserted that the returned logger was of type CustomLoggerDBEnabled.
But it passed! That’s not right I said and reran. Then recompiled. Then restarted visual studio literally thinking something must have gotten cached and the test wasn’t rebuilding or something crazy like that. Then it hit me, maybe null behaves weird with enums. So I debugged it and sure enough the value I passed in as Nothing was being view is 0 in the debugger.
If that’s not bad enough, my enums were “auto enums” (I didn’t define a value for them) and so they started at 0, meaning that the first item in my enum was more or less equal to zero, the next equal to one and so on. As it turns out DATABASE was the first item in my LoggingMode enum and therefore it got the value 0. So in my factory, if it was sent Nothing for the enum it would understand that as 0 and therefore translate it to DATABASE when it came to enum comparisons. All of that happening at the same time, and the fact that I just happened to choose DATABASE as my FAILING condition made the test pass.
The fix was to update my enums to hardcode the value. so instead of:
Public Enum LoggingMode
I did the following:
Public Enum LoggingMode
' Today I learned that sending Nothing as a parameter when an Enum is expected defaults the value to '0'
' This means that the first item in the list of enums will be selected.
' So If we want to send 'Nothing' and have it not match anything in the enums list then we
' have to specify the enum values. This is poop, but it is what it is.
DATABASE = 1
DEVNULL = 2
STANDARD_IO = 3
Now, since I don’t have a value mapped to 0 the Nothing enum will fall all the way to my final else statement and give back the default logger I actually want.
To close this up let me point something not quite so obvious out. Besides the in your face lesson of Enums that are nothing are really 0 there’s something else valuable to take away. Unit Testing works to make better code. You shouldn’t skip it. It finds weird little cases more than I’d like to admit. It just so happens that this one gave me an opportunity to learn a little unrealized nugget about the language rather than the implementation. But it has value! Do it!