Recently I've do some work on a WPF app which required to support multiple languages in the UI - a spike to see how easy supporting multiple languages is in WPF, the test UI is shown below in English & French:
The app is easy enough to understand - change the UI language and all the text based values are updated with the locale specific instances. I wanted to follow the standard pattern of using language specific resource files to store the translated text, these were placed in the standard location inside the project structure:
One of the big advantages of using this mechanism is the resources are compiled into a class which can be used in your ViewModels and Views and this is why see above the 'Resource.resx' file - this contains the 'untranslated' resource strings etc:
I've been aware for a long time there are 2 culturing settings per thread in .Net - Thread.CurrentCulture & Thread.CurrentUICulture, the second is the one of interest here, it is used by the ResourceManager to load the correct resources at run-time. What's interesting is in .Net 4.5 they introduced a couple of static methods to set these properties globally - CultureInfo.DefaultThreadCurrentCulture & CultureInfo.DefaultThreadCurrentUICulture, once set these are used as the default setting for all threads created after this point. This brings up an interesting scenario - if you create a thread before initializing these properties it will default to the behavior seen in .Net 4.0, it will take the current culture from the system locale (windows system culture), but once set then any existing threads will be updated (in the same app-domain). The default value for both properties is NULL, I was expecting it to be set to the system locale.
To keep my ViewModels simple, I decided to push the complexity of using these new properties into a service which is able to notify any interested ViewModels when the selected culture has changed:
As you can see I'm using the CultureChanged property to notify anyone interested when the thread UI culture has changed. To make sure the same instance is shared between all ViewModel instances, the interface & class are registered as a singleton with the IoC container (Autofac):
This then allows a ViewModel to update any text which needs to be translated when the current culture changes, the following snippet shows the CultureChanged property being subscribed too and when the stream pumps it raises IPNC for all the required fields on the ViewModel:
So the point of the post was testing when the culture changes and this is done by putting the implementation of the ICultureService interface under test, this was easy as the service has a high level of SRP, infact I only need 3 tests to cover off the behaviour. The first two are straight forward they test when the culture changes we get notified via the CultureChanged property:
As you can see from the green icons these tests are passing as expected - the icons are a visualisation provided by nCrunch.
The third test produced the interesting results, this test was not specifically testing the CultureService, it was more around testing the vertical slice of changing culture - when the culture changes then the ResourceManager should provide the correctly translated text when requested.
To test this requires translated resource strings in culture specific *.resx files, the following screenshot shows resources for English & French and the test:
Does the test pass or fail?
It depends...
When run as a single test from either nCrunch, Resharper or any other test runner it works as expected:
But when run as part of a group of tests - running all tests in a class or assembly then it will fail:
How can this be explained?
I thought it must be because the ResourceManager only gets updated on the dispatcher thread - I was thinking single tests are run on the UI (dispatcher) thread, where as if your running multiple tests they are run on a background thread. A quick look at the threads running threads showed this wasn't the case, single test:
Multiple tests running (also on a work thread):
At this point I gave up looking for a reason for the failure and started looking for aworkaround solution to getting it working when running multiple tests, the answer turned out to be simple - explicitly set the compiled Resource class Culture property, this is done in a CultureChanged property subscription:
The updated test now looks like this:
One of the big advantages of using this mechanism is the resources are compiled into a class which can be used in your ViewModels and Views and this is why see above the 'Resource.resx' file - this contains the 'untranslated' resource strings etc:
I've been aware for a long time there are 2 culturing settings per thread in .Net - Thread.CurrentCulture & Thread.CurrentUICulture, the second is the one of interest here, it is used by the ResourceManager to load the correct resources at run-time. What's interesting is in .Net 4.5 they introduced a couple of static methods to set these properties globally - CultureInfo.DefaultThreadCurrentCulture & CultureInfo.DefaultThreadCurrentUICulture, once set these are used as the default setting for all threads created after this point. This brings up an interesting scenario - if you create a thread before initializing these properties it will default to the behavior seen in .Net 4.0, it will take the current culture from the system locale (windows system culture), but once set then any existing threads will be updated (in the same app-domain). The default value for both properties is NULL, I was expecting it to be set to the system locale.
To keep my ViewModels simple, I decided to push the complexity of using these new properties into a service which is able to notify any interested ViewModels when the selected culture has changed:
Loading ....
As you can see I'm using the CultureChanged property to notify anyone interested when the thread UI culture has changed. To make sure the same instance is shared between all ViewModel instances, the interface & class are registered as a singleton with the IoC container (Autofac):
This then allows a ViewModel to update any text which needs to be translated when the current culture changes, the following snippet shows the CultureChanged property being subscribed too and when the stream pumps it raises IPNC for all the required fields on the ViewModel:
So the point of the post was testing when the culture changes and this is done by putting the implementation of the ICultureService interface under test, this was easy as the service has a high level of SRP, infact I only need 3 tests to cover off the behaviour. The first two are straight forward they test when the culture changes we get notified via the CultureChanged property:
The third test produced the interesting results, this test was not specifically testing the CultureService, it was more around testing the vertical slice of changing culture - when the culture changes then the ResourceManager should provide the correctly translated text when requested.
To test this requires translated resource strings in culture specific *.resx files, the following screenshot shows resources for English & French and the test:
Does the test pass or fail?
It depends...
When run as a single test from either nCrunch, Resharper or any other test runner it works as expected:
But when run as part of a group of tests - running all tests in a class or assembly then it will fail:
How can this be explained?
I thought it must be because the ResourceManager only gets updated on the dispatcher thread - I was thinking single tests are run on the UI (dispatcher) thread, where as if your running multiple tests they are run on a background thread. A quick look at the threads running threads showed this wasn't the case, single test:
Multiple tests running (also on a work thread):
At this point I gave up looking for a reason for the failure and started looking for a
The updated test now looks like this:
Comments
Post a Comment