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 BasicImports 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 BasicDim 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 BasicDim 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 BasicDim 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:
CodeDevExpress.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 BasiclistEditorMock.Setup(Function(e) e.SupportsDataAccessMode(It.IsAny(Of CollectionSourceDataAccessMode)())).Returns(True)
Now, we get an ArgumentNull exception with the following callstack.
Codemscorlib.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 BasicDim 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:
CodeDevExpress.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 BasicobjectSpaceMock.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 BasiclistEditorMock.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:
- How to write unit tests for XAF Actions, Controllers, and other custom UI logic
- How to unit test Action's enabled/disabled state based on user permissions
- How to unit test whether object property changes via Actions were successfully committed
- How to unit test object queries by criteria and Detail View creation
- How to unit test event handlers in Controllers
- How to unit test New Action's custom business logic based on parent and nested Views
- How to unit test Action's enabled/disabled state based on target criteria and selection dependency types in List View (current)
- How to unit test Action's enabled/disabled state based on target criteria and selection dependency types in Detail View
- How to unit test localized strings from CaptionHelper (Approach 1)
- How to unit test localized strings from CaptionHelper (Approach 2)
- How to unit test custom logic in XAF/XPO business classes
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
public bool SetView(View view);
method like in example. And
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,
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,
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.
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.