KB Article T853714
Visible to All Users

How to unit test Action's enabled/disabled state based on target criteria and selection dependency types in List View

Test Scenario
Let's assume - we have a Controller that creates an Action enabled if a record is selected in the List View and the record meets a particular criterion. Our task is to ensure that the Action is correctly enabled/disabled.

Unit Test Implementation
In this article, we won't create a Controller in our Module. Instead, we will put all logic to test this scenario in our test method. Let's start with creating a test fixture class for our test.

C#
using DevExpress.Data.Filtering.Helpers; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Actions; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.SystemModule; using DevExpress.Xpo; using MySolution.Module.BusinessObjects; using Moq; using NUnit.Framework; using System.Collections.Generic; namespace MySolution.Tests { [TestFixture] public class ActionsCriteriaTests { } }
Visual Basic
Imports DevExpress.Data.Filtering.Helpers Imports DevExpress.ExpressApp Imports DevExpress.ExpressApp.Actions Imports DevExpress.ExpressApp.Editors Imports DevExpress.ExpressApp.SystemModule Imports DevExpress.Xpo Imports MySolution.[Module].BusinessObjects Imports Moq Imports NUnit.Framework Namespace MySolution.Tests <TestFixture> Public Class ActionsCriteriaTests End Class End Namespace

Now, let's create a test method and a tested Action along with its Controller in this test. We will pass several sets of the Action SelectionDependencyType, and TargetObjectsCriteriaMode property values, and also the excepted Action Enabled state. For this, we use the Moq TestCase attribute. The initial setup for our test will be as follows:

C#
[Test] [TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueAtLeastForOne, true)] [TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueForAll, false)] [TestCase(SelectionDependencyType.RequireSingleObject, TargetObjectsCriteriaMode.TrueAtLeastForOne, false)] [TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueAtLeastForOne, true)] [TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueForAll, false)] public void ActionsInListViewTest(SelectionDependencyType dependencyType, TargetObjectsCriteriaMode criteriaMode, bool result) { ViewController testedController = new ViewController(); SimpleAction testedAction = new SimpleAction(testedController, "High Priority", string.Empty); testedAction.TargetObjectsCriteria = "Priority = 2"; testedAction.SelectionDependencyType = dependencyType; testedAction.TargetObjectsCriteriaMode = criteriaMode; // ... additional logic Assert.AreEqual(result, testedAction.Enabled.ResultValue); }
Visual Basic
<Test> <TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueAtLeastForOne, True)> <TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueForAll, False)> <TestCase(SelectionDependencyType.RequireSingleObject, TargetObjectsCriteriaMode.TrueAtLeastForOne, False)> <TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueAtLeastForOne, True)> <TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueForAll, False)> Public Sub ActionsInListViewTest(ByVal dependencyType As SelectionDependencyType, ByVal criteriaMode As TargetObjectsCriteriaMode, ByVal result As Boolean) Dim testedController As ViewController = New ViewController() Dim testedAction As SimpleAction = New SimpleAction(testedController, "High Priority", String.Empty) testedAction.TargetObjectsCriteria = "Priority = 2" testedAction.SelectionDependencyType = dependencyType testedAction.TargetObjectsCriteriaMode = criteriaMode '... additional logic Assert.AreEqual(result, testedAction.Enabled.ResultValue) End Sub

ActionsCriteriaViewController enables/disables an Action based on its target criteria. We will need to create it.

C#
ActionsCriteriaViewController actionsCriteriaViewController = new ActionsCriteriaViewController();
Visual Basic
Dim actionsCriteriaViewController As ActionsCriteriaViewController = New ActionsCriteriaViewController()

Actions update their state when a View is activated in a Frame. Let's create a Frame instance, pass the created Controllers to it, and assign a View to this Frame. The Frame class constructor is:

C#
public Frame(XafApplication application, TemplateContext context, params Controller[] controllers);

We need an XafApplication instance to create a Frame. Let's create an XafApplication mock and create a Frame with our Controllers.

C#
var applicationMock = new Mock<XafApplication>(); Frame frame = new Frame(applicationMock.Object, TemplateContext.View, actionsCriteriaViewController, testedController);
Visual Basic
Dim applicationMock = New Mock(Of XafApplication)() Dim frame As Frame = New Frame(applicationMock.Object, TemplateContext.View, actionsCriteriaViewController, testedController)

Now, assign a View to the created Frame. In this test, we will create a List View. Its constructor is:

C#
public ListView(CollectionSourceBase collectionSource, ListEditor listEditor);

To create a List View, we need CollectionSource and ListEditor instances. Take a look at the CollectionSource constructor.

C#
public CollectionSource(IObjectSpace objectSpace, Type objectType);

To create a CollectionSource instance, we need ObjectSpace and the type of objects that will be contained in the created CollectionSource. Thus, we need to mock ObjectSpace. Our logic does not rely on a particular List Editor type. To create List Editor, it's sufficient to create a mock for the abstract ListEditor type. As a result, our code will be the following:

C#
var listEditorMock = new Mock<ListEditor>(); var objectSpaceMock = new Mock<IObjectSpace>(); frame.SetView(new ListView(new CollectionSource(objectSpaceMock.Object, typeof(DemoTask)), listEditorMock.Object));
Visual Basic
Dim listEditorMock = New Mock(Of ListEditor)() Dim objectSpaceMock = New Mock(Of IObjectSpace)() frame.SetView(New ListView(New CollectionSource(objectSpaceMock.Object, GetType(DemoTask)), listEditorMock.Object))

We created all the things that our logic requires. However, if we run our test, it fails. We need to mock XAF internal methods to make our test work. To see what we need to mock, let's debug the test. To efficiently do this, follow the How can I debug DevExpress .NET source code using PDB files article and research our code in the \DevExpress.ExpressApp\DevExpress.ExpressApp folder.

The first exception will be NullReferenceException with the following callstack:

Code
DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.ListView.RefreshEditorDataSource() Line 495 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.ListView.Editor.set(DevExpress.ExpressApp.Editors.ListEditor value) Line 853 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.ListView.ListView(DevExpress.ExpressApp.CollectionSourceBase collectionSource, DevExpress.ExpressApp.Editors.ListEditor listEditor, bool isRoot, DevExpress.ExpressApp.XafApplication application) Line 697 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.ListView.ListView(DevExpress.ExpressApp.CollectionSourceBase collectionSource, DevExpress.ExpressApp.Editors.ListEditor listEditor, bool isRoot) Line 700 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.ListView.ListView(DevExpress.ExpressApp.CollectionSourceBase collectionSource, DevExpress.ExpressApp.Editors.ListEditor listEditor) Line 703 C#

The ListView.RefreshEditorDataSource implementation is:

C#
protected void RefreshEditorDataSource() { if((editor != null) && (collectionSource != null)) { if(!editor.SupportsDataAccessMode(collectionSource.DataAccessMode) ) { throw new InvalidOperationException(String.Format("The '{0}' used in the '{1}' does not support {2} Mode.", editor.GetType().FullName, model.Id, collectionSource.DataAccessMode)); } Object collection = collectionSource.Collection; if((editor.DataSource != collection) && (collectionSource.ObjectSpace is BaseObjectSpace)) { SubscribeToListServerEvents(((BaseObjectSpace)collectionSource.ObjectSpace).GetListServer(collection)); } editor.DataSource = collection; } }

Line 495 is throw new InvalidOperationException(String.Format("The '{0}' used in the '{1}' does not support {2} Mode.", editor.GetType().FullName, model.Id, collectionSource.DataAccessMode));. The model variable is null and we can't obtain its Id property. However, if we mock the ListEditor SupportsDataAccessMode method to return true, we can bypass the problematic line. We can do this because the method is virtual:

C#
public virtual Boolean SupportsDataAccessMode(CollectionSourceDataAccessMode dataAccessMode) { return DataAccessModeHelper.SupportsDataAccessMode(this.GetType(), this.ObjectTypeInfo as TypeInfo, dataAccessMode); }

Let's do this and run our test again.

C#
listEditorMock.Setup(e => e.SupportsDataAccessMode(It.IsAny<CollectionSourceDataAccessMode>())).Returns(true);
Visual Basic
listEditorMock.Setup(Function(e) e.SupportsDataAccessMode(It.IsAny(Of CollectionSourceDataAccessMode)())).Returns(True)

Now, we get an ArgumentNull exception with the following callstack.

Code
mscorlib.dll!System.Collections.ArrayList.ArrayList(System.Collections.ICollection c) Unknown DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.ListView.SelectedObjects.get() Line 793 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.UpdateAction(DevExpress.ExpressApp.Actions.ActionBase action, string criteria) Line 237 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.UpdateActionState() Line 175 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.Frame_ViewChanged(object sender, DevExpress.ExpressApp.ViewChangedEventArgs e) Line 77 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.OnViewChanged(DevExpress.ExpressApp.Frame sourceFrame) Line 215 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view, bool updateControllers, DevExpress.ExpressApp.Frame sourceFrame, bool disposeOldView) Line 463 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view, bool updateControllers, DevExpress.ExpressApp.Frame sourceFrame) Line 427 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view, DevExpress.ExpressApp.Frame sourceFrame) Line 424 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view) Line 421 C#

The ListView.SelectedObjects property is implemented as follows:

C#
public override IList SelectedObjects { get { if(Editor != null) { return ArrayList.ReadOnly(new ArrayList(Editor.GetSelectedObjects())); } else { return emptyReadOnlyCollection; } } }

The exception occurs because the Editor.GetSelectedObjects method returns null (we can evaluate this in the Watch window). The ListEditor GetSelectedObjects method is abstract:

C#
public abstract IList GetSelectedObjects();

We can mock to return some objects:

C#
List<DemoTask> tasks = new List<DemoTask> { new DemoTask(Session.DefaultSession) {Priority = Priority.High}, new DemoTask(Session.DefaultSession) {Priority = Priority.Low} }; listEditorMock.Setup(e => e.GetSelectedObjects()).Returns(tasks);
Visual Basic
Dim tasks As List(Of DemoTask) = New List(Of DemoTask) From { New DemoTask(Session.DefaultSession) With { .Priority = Priority.High }, New DemoTask(Session.DefaultSession) With { .Priority = Priority.Low } } listEditorMock.Setup(Function(e) e.GetSelectedObjects()).Returns(tasks)

Let's run our test. Now, we get the NullReferenceException with the following callstack:

Code
DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.EvaluatorContext.GetPropertyValue(DevExpress.Data.Filtering.Helpers.EvaluatorProperty propertyPath) Line 239 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCore.Visit(DevExpress.Data.Filtering.OperandProperty theOperand) Line 474 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.OperandProperty.Accept<object>(DevExpress.Data.Filtering.ICriteriaVisitor<object> visitor) Line 1800 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCoreBase.Process(DevExpress.Data.Filtering.CriteriaOperator operand) Line 540 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCoreBase.Visit(DevExpress.Data.Filtering.BinaryOperator theOperator) Line 592 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.BinaryOperator.Accept<object>(DevExpress.Data.Filtering.ICriteriaVisitor<object> visitor) Line 2460 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCoreBase.Process(DevExpress.Data.Filtering.CriteriaOperator operand) Line 540 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCoreBase.Evaluate(DevExpress.Data.Filtering.Helpers.EvaluatorContext evaluationContext, DevExpress.Data.Filtering.CriteriaOperator evaluatorCriteria, System.Collections.IComparer customComparer) Line 1110 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCoreBase.Evaluate(DevExpress.Data.Filtering.Helpers.EvaluatorContext evaluationContext, DevExpress.Data.Filtering.CriteriaOperator evaluatorCriteria) Line 1103 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluatorCoreBase.Fit(DevExpress.Data.Filtering.Helpers.EvaluatorContext evaluationContext, DevExpress.Data.Filtering.CriteriaOperator filterCriteria) Line 1154 C# DevExpress.Data.v19.2.dll!DevExpress.Data.Filtering.Helpers.ExpressionEvaluator.Fit(object theObject) Line 1300 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.IsObjectFitToCriteria(DevExpress.Data.Filtering.Helpers.ExpressionEvaluator criteriaExpression, object obj) Line 228 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.UpdateAction(DevExpress.ExpressApp.Actions.ActionBase action, string criteria) Line 246 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.UpdateActionState() Line 175 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.SystemModule.ActionsCriteriaViewController.Frame_ViewChanged(object sender, DevExpress.ExpressApp.ViewChangedEventArgs e) Line 77 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.OnViewChanged(DevExpress.ExpressApp.Frame sourceFrame) Line 215 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view, bool updateControllers, DevExpress.ExpressApp.Frame sourceFrame, bool disposeOldView) Line 463 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view, bool updateControllers, DevExpress.ExpressApp.Frame sourceFrame) Line 427 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view, DevExpress.ExpressApp.Frame sourceFrame) Line 424 C# DevExpress.ExpressApp.v19.2.dll!DevExpress.ExpressApp.Frame.SetView(DevExpress.ExpressApp.View view) Line 421 C#

To see how we can bypass this, let's check what the top most XAF related method does. This method is ActionsCriteriaViewController.IsObjectFitToCriteria with the following implementation.

C#
protected Boolean IsObjectFitToCriteria(ExpressionEvaluator criteriaExpression, object obj) { bool isFit = false; try { isFit = criteriaExpression.Fit(obj); } catch { } return isFit; }

It just delegates a call to the ExpressionEvaluator object passed as a parameter. Let's check the previous method in the callstack. It's ActionsCriteriaViewController.UpdateAction and has the following code:

C#
ExpressionEvaluator criteriaExpression = GetCriteriaExpression(criteria, action.SelectionContext); if(action.TargetObjectsCriteriaMode == TargetObjectsCriteriaMode.TrueAtLeastForOne) { enable = false; foreach(object obj in selectedObjects) { if(IsObjectFitToCriteria(criteriaExpression, obj)) { enable = true; break; } } } else { enable = true; foreach(object obj in selectedObjects) { if(!IsObjectFitToCriteria(criteriaExpression, obj)) { enable = false; break; } } }

The GetCriteriaExpression method creates an ExpressionEvaluator instance. Take a look at this method.

C#
private ExpressionEvaluator GetCriteriaExpression(String criteria, ISelectionContext selectionContext) { EvaluatorContextDescriptor evaluatorContextDescriptor = null; Type objectType = (selectionContext is ObjectView) ? ((ObjectView)selectionContext).ObjectTypeInfo.Type : ((ObjectView)View).ObjectTypeInfo.Type; ListView listView = selectionContext as ListView; if((listView == null) || ((listView.CollectionSource.DataAccessMode == CollectionSourceDataAccessMode.Client) || (listView.CollectionSource.DataAccessMode == CollectionSourceDataAccessMode.Server))) { evaluatorContextDescriptor = ObjectSpace.GetEvaluatorContextDescriptor(objectType); } else { // ... } ExpressionEvaluator expression = new ExpressionEvaluator(evaluatorContextDescriptor, LocalizedCriteriaWrapper.ParseCriteriaWithReadOnlyParameters(criteria, objectType)); return expression; }

If the current View is a Detail View or the List View DataAccessMode is Client (by default), the ObjectSpace.GetEvaluatorContextDescriptor method is called to create an EvaluatorContextDescriptor instance. We didn't provide this method implementation in our ObjectSpace mock. As a result, it returns null. We need to mock this method. BaseObjectSpace returns an EvaluatorContextDescriptorDefault instance in this method. Let's do the same.

C#
objectSpaceMock.Setup(os => os.GetEvaluatorContextDescriptor(typeof(DemoTask))).Returns(new EvaluatorContextDescriptorDefault(typeof(DemoTask)));
Visual Basic
objectSpaceMock.Setup(Function(os) os.GetEvaluatorContextDescriptor(GetType(DemoTask))).Returns(New EvaluatorContextDescriptorDefault(GetType(DemoTask)))

Now, there are no exceptions, but our test returns an unexpected result for the [TestCase(SelectionDependencyType.RequireSingleObject, TargetObjectsCriteriaMode.TrueAtLeastForOne, false)] case. To debug this case, put a breakpoint in the ActionsCriteriaViewController.UpdateAction method:

C#
protected virtual void UpdateAction(ActionBase action, string criteria) { if(action.Active && (action.Enabled || action.Enabled.Contains(EnabledByCriteriaKey))) { // ... } }

The first time this method is called, the Action is inactive (the Active property returns False) due to the "Controller active" reason. We are not interested in this case since our Controller is just not activated.

The second time this method is called, the Action is inactive due to the "ByContext_RequireSingleObject" reason. Let's search for this key in our source code. It's used in the ActionBase UpdateState method. Put a breakpoint in this method. Its interesting code is the following:

C#
protected internal void UpdateState() { // ... try { // ... if(selectionContext != null) { SelectionType selectionType = selectionContext.SelectionType; bool actionIsActive = (SelectionType.MultipleSelection & selectionType) == SelectionType.MultipleSelection || ((SelectionType.TemporarySelection & selectionType) == SelectionType.TemporarySelection && selectionContext.SelectedObjects.Count == 1); switch(SelectionDependencyType) { case SelectionDependencyType.RequireSingleObject: { if(selectionContext.SelectionType != SelectionType.None) { Enabled[RequireSingleObjectContext] = selectionContext.SelectedObjects.Count == 1; RaiseChanged(ActionChangedType.ConfirmationMessage); } Active[RequireSingleObjectContext] = actionIsActive; break; } case SelectionDependencyType.RequireMultipleObjects: { // ... } break; } } } finally { // ... } }

selectionContext is our List View. Let's check how the SelectionType property is implemented in the ListView class:

C#
public override SelectionType SelectionType { get { if(Editor == null) { return base.SelectionType; } else { return Editor.SelectionType; } } }

The ListEditor SelectionType property is abstract:

C#
public abstract SelectionType SelectionType { get; }

It returns the default SelectionType enumeration value, which is None. That's why our Action is inactive. Since the ListEditor SelectionType property is abstract, we can mock it to provide the required value:

C#
listEditorMock.Setup(e => e.SelectionType).Returns(SelectionType.Full);
Visual Basic
listEditorMock.Setup(Function(e) e.SelectionType).Returns(SelectionType.Full)

Now, our test passes! Our entire test code will be:

C#
[Test] [TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueAtLeastForOne, true)] [TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueForAll, false)] [TestCase(SelectionDependencyType.RequireSingleObject, TargetObjectsCriteriaMode.TrueAtLeastForOne, false)] [TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueAtLeastForOne, true)] [TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueForAll, false)] public void ActionsInListViewTest(SelectionDependencyType dependencyType, TargetObjectsCriteriaMode criteriaMode, bool result) { ViewController testedController = new ViewController(); SimpleAction testedAction = new SimpleAction(testedController, "High Priority", string.Empty); testedAction.TargetObjectsCriteria = "Priority = 2"; testedAction.SelectionDependencyType = dependencyType; testedAction.TargetObjectsCriteriaMode = criteriaMode; List<DemoTask> tasks = new List<DemoTask> { new DemoTask(Session.DefaultSession) {Priority = Priority.High}, new DemoTask(Session.DefaultSession) {Priority = Priority.Low} }; ActionsCriteriaViewController actionsCriteriaViewController = new ActionsCriteriaViewController(); var applicationMock = new Mock<XafApplication>(); Frame frame = new Frame(applicationMock.Object, TemplateContext.View, actionsCriteriaViewController, testedController); var listEditorMock = new Mock<ListEditor>(); listEditorMock.Setup(e => e.GetSelectedObjects()).Returns(tasks); listEditorMock.Setup(e => e.SupportsDataAccessMode(It.IsAny<CollectionSourceDataAccessMode>())).Returns(true); listEditorMock.Setup(e => e.SelectionType).Returns(SelectionType.Full); var objectSpaceMock = new Mock<IObjectSpace>(); objectSpaceMock.Setup(os => os.GetEvaluatorContextDescriptor(typeof(DemoTask))).Returns(new EvaluatorContextDescriptorDefault(typeof(DemoTask))); frame.SetView(new ListView(new CollectionSource(objectSpaceMock.Object, typeof(DemoTask)), listEditorMock.Object)); Assert.AreEqual(result, testedAction.Enabled.ResultValue); }
Visual Basic
<Test> <TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueAtLeastForOne, True)> <TestCase(SelectionDependencyType.Independent, TargetObjectsCriteriaMode.TrueForAll, False)> <TestCase(SelectionDependencyType.RequireSingleObject, TargetObjectsCriteriaMode.TrueAtLeastForOne, False)> <TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueAtLeastForOne, True)> <TestCase(SelectionDependencyType.RequireMultipleObjects, TargetObjectsCriteriaMode.TrueForAll, False)> Public Sub ActionsInListViewTest(ByVal dependencyType As SelectionDependencyType, ByVal criteriaMode As TargetObjectsCriteriaMode, ByVal result As Boolean) Dim testedController As ViewController = New ViewController() Dim testedAction As SimpleAction = New SimpleAction(testedController, "High Priority", String.Empty) testedAction.TargetObjectsCriteria = "Priority = 2" testedAction.SelectionDependencyType = dependencyType testedAction.TargetObjectsCriteriaMode = criteriaMode Dim tasks As List(Of DemoTask) = New List(Of DemoTask) From { New DemoTask(Session.DefaultSession) With { .Priority = Priority.High }, New DemoTask(Session.DefaultSession) With { .Priority = Priority.Low } } Dim actionsCriteriaViewController As ActionsCriteriaViewController = New ActionsCriteriaViewController() Dim applicationMock = New Mock(Of XafApplication)() Dim frame As Frame = New Frame(applicationMock.Object, TemplateContext.View, actionsCriteriaViewController, testedController) Dim listEditorMock = New Mock(Of ListEditor)() listEditorMock.Setup(Function(e) e.GetSelectedObjects()).Returns(tasks) listEditorMock.Setup(Function(e) e.SupportsDataAccessMode(It.IsAny(Of CollectionSourceDataAccessMode)())).Returns(True) listEditorMock.Setup(Function(e) e.SelectionType).Returns(SelectionType.Full) Dim objectSpaceMock = New Mock(Of IObjectSpace)() objectSpaceMock.Setup(Function(os) os.GetEvaluatorContextDescriptor(GetType(DemoTask))).Returns(New EvaluatorContextDescriptorDefault(GetType(DemoTask))) frame.SetView(New ListView(New CollectionSource(objectSpaceMock.Object, GetType(DemoTask)), listEditorMock.Object)) Assert.AreEqual(result, testedAction.Enabled.ResultValue) 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 (current)
  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)
  10. How to unit test localized strings from CaptionHelper (Approach 2)
  11. How to unit test custom logic in XAF/XPO business classes
Comments (2)
KY KY
Kalem Yazılım 5 years ago

    Hello XAF Team,

    First of all, thank you for your detailed information about how to use NUnit framework and Moq library in xaf application testing.

    In my real case scenario, there are some differences on this example. Both of my controller and action in this controller have TargetViewId requirement. ( Lets say PurchaseInvoice_ListView )

    Let me detail which state i am in,

    I merged current topic and this topic solutions ( for accessing model ) but couldn't success.

    Firstly I thought I should use Frame's

    C#
    public bool SetView(View view);

    method like in example. And

    C#
    public ListView(IModelListView modelListView, CollectionSourceBase collectionSource, XafApplication application, bool isRoot);

    this constructor for creating ListView, because Frame should be informed which listview's on it. Now I need IModelListView and from my knowledge I can access this object via application model and used this,

    C#
    IModelListView purchaseInvoiceListView = (IModelListView)applicationMock.Object.Model.Views.First(a => a.Id == "PurchaseInvoice_ListView");

    As you know, mocked application's Model property comes null. I used code in 9. NUnit sample for initializing model like this,

    C#
    ModelApplicationCreatorProperties properties = ModelApplicationCreatorProperties.CreateDefault(); ModelApplicationCreator modelApplicationCreator = ModelApplicationCreator.GetModelApplicationCreator(properties); ModelApplicationBase modelApplicationBase = modelApplicationCreator.CreateModelApplication(); IModelApplication modelApplication = (IModelApplication)modelApplicationBase; applicationMock.Setup(a => a.Model).Returns(modelApplication);

    I changed the first line in this code according to your sample, I thought some code changes occures on your side because ModelApplicationCreatorPropertiesHelper not exists in 20.1.4 version of your source code which I am using. And mocked Model property successfully.

    After that, test failing again because Views property comes null and I couldn't find how xaf populates this property. There are 17 references in CommonInterfaces.cs IModelApplication class Views property. But no ones routed me to solution.

    Could you please help me? Either with additional code to my works or solution from scratch while controller and action has TargetViewId.

    Have a nice day.

    DevExpress Support Team 5 years ago

      Hello Kalem,

      I've created a separate ticket on your behalf (T915268: How to test a controller with the TargetViewId property specified and get a localized string from CaptionHelper). It has been placed in our processing queue and will be answered shortly.

      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.