KB Article T853968
Visible to All Users

How to unit test localized strings from CaptionHelper (Approach 1)

Test Scenario
We localized certain strings in an XAF application. We want to ensure that our localized string is correctly applied.

Unit Test Implementation
Imagine we added a localization item and we want to test that CaptionHelper returns the correct localized string.

XML
<Application Title="MySolution"> <Localization> <LocalizationGroup Name="Messages"> <LocalizationItem Name="Test" Value="Test" IsNewNode="True" /> </LocalizationGroup> </Localization> </Application>

Our test will be:

C#
using DevExpress.ExpressApp.Model; using DevExpress.ExpressApp.Model.Core; using DevExpress.ExpressApp.Utils; using NUnit.Framework; using System; using MySolution.Module; namespace MySolution.Tests { [TestFixture] public class CaptionHelperTests { [Test] public void Test1() { Assert.AreEqual("Test", CaptionHelper.GetLocalizedText("Messages", "Test")); } } }
Visual Basic
Imports DevExpress.ExpressApp.Model Imports DevExpress.ExpressApp.Model.Core Imports DevExpress.ExpressApp.Utils Imports NUnit.Framework Imports MySolution.[Module] <TestFixture> Public Class CaptionHelperTests <Test> Public Sub Test1() Assert.AreEqual("Test", CaptionHelper.GetLocalizedText("Messages", "Test")) End Sub End Class

However, if we run our test, the CaptionHelper.GetLocalizedText method returns an empty string and the test fails. To see what's going on and what XAF internal methods we need to call, let's debug this method by following the How can I debug DevExpress .NET source code using PDB files article and search through the XAF code in the \DevExpress.ExpressApp\DevExpress.ExpressApp folder. The CaptionHelper.GetLocalizedText method has the following implementation:

C#
public static string GetLocalizedText(string groupPath, string itemName, string defaultText) { string result = defaultText ?? string.Empty; IModelLocalizationGroup groupNode = FindGroupNode(groupPath); if(groupNode != null) { IModelLocalizationItem itemNode = groupNode[itemName] as IModelLocalizationItem; if(itemNode != null) { if(itemNode.Value != null){ result = itemNode.Value; if(RemoveAcceleratorSymbol) { result = RemoveAccelerator(result); } } } IModelLocalizationGroup childGroupNode = groupNode[itemName] as IModelLocalizationGroup; if(childGroupNode != null) { if(childGroupNode.Value != null) { result = childGroupNode.Value; if(RemoveAcceleratorSymbol) { result = RemoveAccelerator(result); } } } } return result.Replace("\\n", "\n").Replace("\\r", "\r"); } public static string GetLocalizedText(string groupPath, string itemName) { return GetLocalizedText(groupPath, itemName, ""); }

If we debug it, we will see that the FindGroupNode method returns null. Let's step into this method.

C#
public static IModelLocalizationGroup FindGroupNode(string groupPath) { return FindGroupNode(ApplicationModel, groupPath); } public static IModelLocalizationGroup FindGroupNode(IModelApplication model, string groupPath) { IModelApplication applicationNode = model; if(applicationNode == null) return null; IModelLocalization localizationNode = applicationNode.Localization; Guard.Assert(localizationNode != null, "localizationNode is null"); IModelLocalizationGroup groupNode = localizationNode[groupPath]; if(!string.IsNullOrEmpty(groupPath)) { string[] pathItems = groupPath.Replace('\\', '/').Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if(pathItems.Length > 1) { foreach(string groupName in pathItems) { groupNode = groupNode == null ? localizationNode[groupName] : (IModelLocalizationGroup)groupNode[groupName]; if(groupNode == null) break; } } } return groupNode; } public static IModelApplication ApplicationModel { get { return ValueManager.GetValueManager<IModelApplication>("CaptionHelper_IModelApplication").Value; } }

We will see that ApplicationModel is null. Nothing assigned it to ValueManager.GetValueManager<IModelApplication>("CaptionHelper_IModelApplication").Value. If we search for this line in the CaptionHelper class, we will find that this assignment happens in the Setup method:

C#
public static void Setup(IModelApplication applicationModel) { ValueManager.GetValueManager<IModelApplication>("CaptionHelper_IModelApplication").Value = applicationModel; }

Let's call it in our test:

C#
IModelApplication modelApplication; CaptionHelper.Setup(modelApplication);
Visual Basic
Dim modelApplication As IModelApplication CaptionHelper.Setup(modelApplication)

But, we need to pass an IModelApplication instance to this method. Let's search through XAF code to see how the CaptionHelper.Setup method is called in it. It's called in the XafApplication InitializeCaptionHelper method, which is called from the XafApplication SetupModelApplication method.

C#
protected virtual void InitializeCaptionHelper(IModelApplication model) { CaptionHelper.Setup(model); } private void SetupModelApplication(IEnumerable<ModelApplicationBase> layers) { if(model != null) { modelManager.DropModel((ModelApplicationBase)model); } ModelApplicationBase result = modelManager.CreateModelApplication(layers); InitializeCurrentAspectProvider(result); ActivateResourceLocalizers((IModelApplication)result); InitializeCaptionHelper((IModelApplication)result); model = (IModelApplication)result; OnModelChanged(); }

So, the ApplicationModelManager CreateModelApplication method can create a ModelApplicationBase instance that implements the IModelApplication interface. Let's take a look at the ApplicationModelManager CreateModelApplication method.

C#
public ModelApplicationBase CreateModelApplication(IEnumerable<ModelApplicationBase> layers) { Guard.Assert(creator != null, "ApplicationModelManager must be set up"); Guard.Assert(layers != null, "'layers' must not be null"); ModelApplicationBase model = CreateLayer(ModelApplicationLayerIds.Application); model.AddLayerInternal(unchangeableLayer); foreach(ModelApplicationBase layer in layers) { Guard.Assert(layer != null, "'layer' must not be null"); model.AddLayerInternal(layer); } AddCustomMembersToTypeInfo((IModelApplication)model); return model; } public ModelApplicationBase CreateLayer(string id) { Guard.Assert(creator != null, "ApplicationModelManager must be set up"); ModelApplicationBase result = creator.CreateModelApplication(); result.Id = id; InitializeModelSources(result); return result; }

It calls the CreateLayer method to create a ModelApplicationBase instance. The CreateLayer method, in its turn, calls the ModelApplicationCreator CreateModelApplication method to do this. We can use this method in our test to create a ModelApplicationBase instance and pass it to the CaptionHelper Setup method. The ModelApplicationCreator class does not have a public constructor. But, it provides the GetModelApplicationCreator static method to obtain its instance. Let's add the corresponding code to our test.

C#
ModelApplicationCreatorProperties properties; ModelApplicationCreator modelApplicationCreator = ModelApplicationCreator.GetModelApplicationCreator(properties); ModelApplicationBase modelApplicationBase = modelApplicationCreator.CreateModelApplication(); IModelApplication modelApplication = (IModelApplication)modelApplicationBase; CaptionHelper.Setup(modelApplication);
Visual Basic
Dim properties As ModelApplicationCreatorProperties Dim modelApplicationCreator As ModelApplicationCreator = modelApplicationCreator.GetModelApplicationCreator(properties) Dim modelApplicationBase As ModelApplicationBase = modelApplicationCreator.CreateModelApplication() Dim modelApplication As IModelApplication = CType(modelApplicationBase, IModelApplication) CaptionHelper.Setup(modelApplication)

Now, we need to create a ModelApplicationCreatorProperties object. If we search for "new ModelApplicationCreatorProperties" in the XAF code, a ModelApplicationCreatorProperties object is created in the static ModelApplicationCreatorProperties CreateDefault method. However, it's internal and we can't call it in our test.

C#
internal static ModelApplicationCreatorProperties CreateDefault() { return new ModelApplicationCreatorProperties(DefaultModelApplicationNodeBaseInterface, DefaultModelApplicationNodeBaseClass); }

Let's search for "ModelApplicationCreatorProperties.CreateDefault" in the XAF source code. We will see that there is the ModelApplicationCreatorPropertiesHelper public class with the Create method that calls the ModelApplicationCreatorProperties CreateDefault method and returns the created ModelApplicationCreatorProperties object.

C#
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static class ModelApplicationCreatorPropertiesHelper { [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static ModelApplicationCreatorProperties Create() { return ModelApplicationCreatorProperties.CreateDefault(); } }

Let's use it in our test code.

C#
ModelApplicationCreatorProperties properties = ModelApplicationCreatorPropertiesHelper.Create(); ModelApplicationCreator modelApplicationCreator = ModelApplicationCreator.GetModelApplicationCreator(properties); ModelApplicationBase modelApplicationBase = modelApplicationCreator.CreateModelApplication(); IModelApplication modelApplication = (IModelApplication)modelApplicationBase; CaptionHelper.Setup(modelApplication); Assert.AreEqual("Test", CaptionHelper.GetLocalizedText("Messages", "Test"));
Visual Basic
Dim properties As ModelApplicationCreatorProperties = ModelApplicationCreatorPropertiesHelper.Create() Dim modelApplicationCreator As ModelApplicationCreator = modelApplicationCreator.GetModelApplicationCreator(properties) Dim modelApplicationBase As ModelApplicationBase = modelApplicationCreator.CreateModelApplication() Dim modelApplication As IModelApplication = CType(modelApplicationBase, IModelApplication) CaptionHelper.Setup(modelApplication) Assert.AreEqual("Test", CaptionHelper.GetLocalizedText("Messages", "Test"))

Let's now run our test. It fails with the 'Assertion broken: localizationNode is null' exception and the following class stack:

Call Stack
DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Utils.Guard.Assert(bool assertion, string message) Line 53 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Utils.CaptionHelper.FindGroupNode(DevExpress.ExpressApp.Model.IModelApplication model, string groupPath) Line 196 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Utils.CaptionHelper.FindGroupNode(string groupPath) Line 188 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Utils.CaptionHelper.GetLocalizedText(string groupPath, string itemName, string defaultText) Line 361 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Utils.CaptionHelper.GetLocalizedText(string groupPath, string itemName) Line 385 C#

This occurs because the IModelApplication Localization property is null in the CaptionHelper FindGroupNode method.

C#
public static IModelLocalizationGroup FindGroupNode(IModelApplication model, string groupPath) { IModelApplication applicationNode = model; if(applicationNode == null) return null; IModelLocalization localizationNode = applicationNode.Localization; Guard.Assert(localizationNode != null, "localizationNode is null"); IModelLocalizationGroup groupNode = localizationNode[groupPath]; if(!string.IsNullOrEmpty(groupPath)) { string[] pathItems = groupPath.Replace('\\', '/').Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if(pathItems.Length > 1) { foreach(string groupName in pathItems) { groupNode = groupNode == null ? localizationNode[groupName] : (IModelLocalizationGroup)groupNode[groupName]; if(groupNode == null) break; } } } return groupNode; }

This happens because we created an application model, but didn't load our model differences from the Model.DesignedDiffs.xafml file. According to the Model Difference Storages article, the ResourcesModelStore class loads model differences made at design time in a Module. Let's search for "ResourcesModelStore" in the XAF source code. We will see that the ModuleBase DiffsStore property returns a ResourcesModelStore object.

C#
public ModelStoreBase DiffsStore { get { if(diffsStore == null) { diffsStore = new ResourcesModelStore(GetType().Assembly, ModelDifferenceResourceName); } return diffsStore; } set { diffsStore = value; } }

So, we need to get a ResourcesModelStore instance from our module and call its Load method. After this, our test will pass. The entire test code is:

C#
[Test] public void Test1() { MySolutionModule module = new MySolutionModule(); ModelApplicationCreatorProperties properties = ModelApplicationCreatorPropertiesHelper.Create(); ModelApplicationCreator modelApplicationCreator = ModelApplicationCreator.GetModelApplicationCreator(properties); ModelApplicationBase modelApplicationBase = modelApplicationCreator.CreateModelApplication(); module.DiffsStore.Load(modelApplicationBase); IModelApplication modelApplication = (IModelApplication)modelApplicationBase; CaptionHelper.Setup(modelApplication); Assert.AreEqual("Test", CaptionHelper.GetLocalizedText("Messages", "Test")); }
Visual Basic
<Test> Public Sub Test1() Dim [module] As MySolutionModule = New MySolutionModule() Dim properties As ModelApplicationCreatorProperties = ModelApplicationCreatorPropertiesHelper.Create() Dim modelApplicationCreator As ModelApplicationCreator = modelApplicationCreator.GetModelApplicationCreator(properties) Dim modelApplicationBase As ModelApplicationBase = modelApplicationCreator.CreateModelApplication() [module].DiffsStore.Load(modelApplicationBase) Dim modelApplication As IModelApplication = CType(modelApplicationBase, IModelApplication) CaptionHelper.Setup(modelApplication) Assert.AreEqual("Test", CaptionHelper.GetLocalizedText("Messages", "Test")) End Sub

See Also
This series contains the following articles:

  1. How to write unit tests for XAF Actions, Controllers, and other custom UI logic
  2. How to unit test Action's enabled/disabled state based on user permissions
  3. How to unit test whether object property changes via Actions were successfully committed
  4. How to unit test object queries by criteria and Detail View creation
  5. How to unit test event handlers in Controllers
  6. How to unit test New Action's custom business logic based on parent and nested Views
  7. How to unit test Action's enabled/disabled state based on target criteria and selection dependency types in List View
  8. How to unit test Action's enabled/disabled state based on target criteria and selection dependency types in Detail View
  9. How to unit test localized strings from CaptionHelper (Approach 1) (current)
  10. How to unit test localized strings from CaptionHelper (Approach 2)
  11. How to unit test custom logic in XAF/XPO business classes

Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.