-
Notifications
You must be signed in to change notification settings - Fork 2
Unit testing your localized code
This document defines different ways to unit test an application which uses the ISimpleLocalizer or IDefaultLocalizer services to localize the languages in the application.
The approaches in this document mainly stubbing out the localization services (see this Microsoft document about stubbing) such that the default message is returned. This allows you to use the default messages in your tests without setting any resource files, which makes testing much easier. Here is a list of the various types and approaches:
- How to test code using the
ISimpleLocalizerservice -
How to test code using the
IDefaultLocalizerservice
This library contains ISimpleLocalizer stub called StubSimpleLocalizer which returns the default message, plus gives you access to the localize key. The code below shows you how you can use the StubSimpleLocalizer in your tests - in this case it gets the default message and the localize key.
[Fact]
public void YourTest()
{
//SETUP
var simpleLoc = new StubSimpleLocalizer();
//ATTEMPT
var message = simpleLoc.LocalizeString("My message", this);
//VERIFY
message.ShouldEqual("My message");
simpleLoc.LastKeyData.LocalizeKey.ShouldEqual(
"SimpleLocalizer(My message)");
}NOTE: The stub contains a LastKeyData parameter that will hold the localize key data. This is useful if you want to check localize keys etc.
You have two ways to provide a stub for the IDefaultLocalizer service. One is a basic stub class called StubDefaultLocalizer and a more sophisticated stub called StubLocalizeDefaultWithLogging which can log detailed information on every message to a database.
The code below, based on from the TestLocalizeStringMessage test class, which returns the default messages and also gives you access to the localize key.
[Fact]
public void TestStubDefaultLocalizer()
{
//SETUP
var defaultLoc = new StubDefaultLocalizer();
//ATTEMPT
var message = defaultLoc.LocalizeStringMessage(
"MyLocalizeKey".MethodLocalizeKey(this),
"My message");
//VERIFY
message.ShouldEqual("My message");
defaultLoc.LastKeyData.LocalizeKey.ShouldEqual(
"TestStubDefaultLocalizer_MyLocalizeKey");
}NOTE: The stub contains a LastKeyData parameter that will hold the localize key data. This is useful if you want to check localize keys etc.
The StubLocalizeDefaultWithLogging returns the default message, but (optionally) logs the full information of the localization data to a database. This provides a quick way to look at the localized messages, and it can find certain problems.
For each use of a DefaultLocalizer / SimpleLocalizer it shows the localize key, culture, the message and where the localised entry was created (the full list of what is in the log can be found in the LocalizedLog class, which has 9 parameters).
There are two forms of logging:
- A list of every localization event during the instance of the stub exists. This is useful for adding extra tests on the localize key data.
- Optionally (off by default) it can log the full localization information to a database. Depending how well your test coverage is, this can provide a detailed list of localize keys / messages which is useful when building your resource files.
NOTE: The StubLocalizeDefaultWithLogging isn't in the NuGet library because I didn't want the library to contain EF Core code. You need to copy the StubLocalizeDefaultWithLogging into your test project and add the EfCore.TestSupport NuGet packages.
The code below shows how to get the log of all the localized messages in a test via the stub's List<LocalizedLog> Logs parameter.
[Fact]
public void TestStubLocalizeDefaultWithLogging_Logs()
{
//SETUP
var stubLocalizer = new StubDefaultLocalizerWithLogging
("en", GetType());
//ATTEMPT
stubLocalizer.LocalizeStringMessage(
"Test1".MethodLocalizeKey(this),
"Hello Earth");
//VERIFY
stubLocalizer.Logs.Count.ShouldEqual(1);
stubLocalizer.Logs[0].ActualMessage.ShouldEqual("Hello Earth");
stubLocalizer.Logs[0].LocalizeKey.ShouldEqual(
"TestStubLocalizeDefaultWithLogging_Logs_Test1");
}I find logging the detailed list of localize keys / messages when running my unit tests is very useful because it gives me the information for setting up the resource files containing the extra languages. The only downside is it will make the unit tests slower - in this library it takes ~1.5 seconds without logging to the database, but ~2 seconds with logging to the database. Thankfully you can turn the database logging on or off by setting the SaveLocalizesToDb to true or false respectively - see next section.
You need to add a appsettings.json to your test project which a) provides the connection string of the database to log to, and b) the "SaveLocalizesToDb" must be true - see example below
{
"ConnectionStrings": {
"LocalizationCaptureDb": "connection string to your database to log to"
},
"SaveLocalizesToDb": true
}There are various ways to look at the logs, including some code in the StubLocalizeDefaultWithLogging, but I mainly read the data via LINQPad or Azure Data Studio. The screenshot shows part of the localization via LINQPad.

NOTE the PossibleErrors column, which will detect an existing entry with the same localise key, but the message has a different message / format.
Inside the StubLocalizeDefaultWithLogging class there are two methods:
-
ListLocalizationCaptureDb, which will return all the entries in the database as a list ofLocalizedLogs. -
WipeLocalizationCaptureDb, which wipes all the entries from the database. If you want to get an overview of the localize data its best to wipe the database and then run all your unit tests. This will ensure you are looking at the latest localization state.
You don't need these methods as you can do the same with any database access code, but if you want to use these then have a look at the LocalizationCaptureCommands. Because I don't these tests to run normally I:
- I add the
[RunnableInDebugOnly]attribute from my EfCore.TestSupport library to these methods which tells xUnit to only run this method unless the test is in Debug mode. - I put my normal tests in a top-level folder called
UnitTestsand any manually run tests (I called them unit commands) in a top-level folder calledUnitCommands. That way I can make sure I don't run the unit commands.
NOTE: If you aren't using my EfCore.TestSupport library then copy the code from Jimmy Bogard's article that I got it from.