Search code examples
cultureinfomspeccurrentculture

How do I execute an MSpec specification in a specific Thread Culture?


I have a few specifications that test code performing culture-aware conversions. I want to set a defined culture for my tests so that I can hard-code the expected values without having to worry about the configured culture of the system running the tests.

Is there a simple way to do this with Machine.Specifications or do I have to set Thread.CurrentThread.CurrentCulture (and possibly CurrentUICulture as well)?


Solution

  • MSpec doesn't have any built-in facilities for changing the Thread's culture. However, it has several general methods for doing "something" before and after a test.

    The "easy" way is to just use the Establish and Cleanup delegates.

    [Subject("Culture Sensitive Tests")]
    public class When_doing_culture_sensitive_stuff
    {
        Establish context = () =>
        {
            OldCulture = Thread.CurrentThread.CurrentCulture;
            Thread.CurrentThread.CurrentCulture = NewCulture;
            Thread.CurrentThread.CurrentUICulture = NewCulture;
        }
    
        Cleanup cleanup = () => 
        {
            Thread.CurrentThread.CurrentCulture = OldCulture;
            Thread.CurrentThread.CurrentUICulture = OldCulture;
        }
    
        Because of = () => Subject.DoSomethingCultureSensitive();
    
        It should_do_something_culture_sensitive = () => ...;
    
        private static CultureInfo OldCulture;
        private static CultureInfo NewCulture;
    }
    

    But, you'd need to share that in every test that requires it. So, I recommend a helper class that does the switching.

    public class ChangeCurrentCulture : IDisposable
    {
        private readonly CultureInfo original;
    
        public ChangeCurrentCulture(CultureInfo culture)
        {
            original = Thread.CurrentThread.CurrentCulture;
            Change(culture)
        }
    
        private void Change(CultureInfo culture)
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
        }
    
        public void Dispose()
        {
            Change(original);
        }
    }
    

    And you can setup the call in a shared base class. The delegates get chained when you run the specs.

    public abstract class CultureSensitive
    {
        Establish context = () =>
        {
            Change = new ChangeCurrentCulture(NewCulture);
        }
    
        Cleanup cleanup = () => 
        {
            Change.Dispose();
        }
    
        private static ChangeCurrentCulture Change;
        private static CultureInfo NewCulture;
    }
    
    
    [Subject("Culture Sensitive Tests")]
    public class When_doing_culture_sensitive_stuff : CultureSensitive
    {   
        Because of = () => Subject.DoSomethingCultureSensitive();
    
        It should_do_something_culture_sensitive = () => ...;
    }
    

    Another option depends on you separating all of the culture sensitive tests into a separate assembly. The IAssemblyContext interface gives you two assembly-wide setup and cleanup methods. You could change the culture for all the specs in that assembly (don't worry about cleanup).

    public class CultureSensitiveTests : IAssemblyContext
    {
        public void OnAssemblyStart()
        {
            Thread.CurrentThread.CurrentCulture = NewCulture;
            Thread.CurrentThread.CurrentUICulture = NewCulture;
        }
    
        public void OnAssemblyComplete()
        {
        }
    }