CurrentThread.CurrentCulture and Unit Testing

I have a bug report where double.Parse(input) is throwing the following exception with the input "0.69803923368454":

FormatException: Unknown char: .
System.Double.Parse (System.String s, NumberStyles style, IFormatProvider provider) (at  /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Double.cs:209)
System.Double.Parse (System.String s) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Double.cs:180)

After some searching it seems that this issue occurs when the default culture does not support the decimal character . (see float.Parse fails on decimals and commas);

I need to create a unit test which reproduces this issue by forcing a different default culture for the duration of the test. Naturally this must not interfere with any existing unit test. I am using the unit testing framework which is provided with Visual Studio.

Here is what I have tried, but unfortunately this does not cause the reported error to occur:

[TestMethod]
private void DoubleParseWithCultureOverride() {
    var restoreCulture = Thread.CurrentThread.CurrentCulture;
    var restoreUICulture = Thread.CurrentThread.CurrentUICulture;
    try {
        // Arrange
        Thread.CurrentThread.CurrentCulture = new CultureInfo("ko-KR");
        Thread.CurrentThread.CurrentUICulture = new CultureInfo("ko-KR");

        // Act
        double value = double.Parse("0.69803923368454");

        // Assert
        Assert.AreEqual(0.69803923368454, value);
    }
    finally {
        Thread.CurrentThread.CurrentCulture = restoreCulture;
        Thread.CurrentThread.CurrentUICulture = restoreUICulture;
    }
}

I was expecting the above unit test to fail (i.e. become red in the test explorer panel), but it passed. At the moment I am purely attempting to force the error with standard Mono/.NET usage. I intend to replace the "Act" section with application specific logic.

Jon Skeet
people
quotationmark

You've just picked a culture which happens to use . as a decimal point:

var culture = new CultureInfo("ko-KR");
Console.WriteLine(culture.NumberFormat.NumberDecimalSeparator); // Prints .

I typically use French (fr-FR) for this - and that does fail with your current code.

Alternatively, you could construct your own CultureInfo specifically for testing, with whatever separator you want.

For testing like this, you might want a simpler way of setting the culture, too. Options:

  • Write a method taking an action to execute "within" a culture, then call it as:

    ExecuteInCulture("fr-Fr", () => 
    {
        // Parse a double, or whatever
    });
    
  • Create an IDisposable implementation which sets the culture and restores it on Dispose:

    using (CultureHelper.SetCulture("fr-FR"))
    {
        // Parse a double, or whatever
    }
    

The former approach is probably cleaner - it's not like you've really got a "resource" here.

people

See more on this question at Stackoverflow