Test Scenario
We have the following Controller that finds an object by a criterion and opens its Detail View on an action click. Our task is to check if we create the correct criterion, if the correct object is found, and if a Detail View is created for this object. We don't need to test that the Action Execute event is raised and that the Detail View is shown. This is XAF responsibility and this scenario should be tested by functional tests.
C#public class FindBySubjectController : ViewController {
private void FindBySubjectAction_Execute(object sender, ParametrizedActionExecuteEventArgs e) {
var createdView = CreateObjectViewBySubject(((ListView)View).ObjectTypeInfo.Type, e.ParameterCurrentValue as string);
e.ShowViewParameters.CreatedView = createdView;
}
public View CreateObjectViewBySubject(Type objectType, string subject) {
CriteriaOperator criteria = CriteriaOperator.Parse($"Contains([Subject], '{subject}')");
IObjectSpace objectSpace = Application.CreateObjectSpace(objectType);
object obj = objectSpace.FindObject(objectType, criteria);
DetailView createdView = null;
if (obj != null) {
createdView = Application.CreateDetailView(objectSpace, obj);
}
return createdView;
}
}
Visual BasicPublic Class FindBySubjectController
Inherits ViewController
Private Sub FindBySubjectAction_Execute(ByVal sender As Object, ByVal e As ParametrizedActionExecuteEventArgs)
Dim createdView As View = CreateObjectViewBySubject(CType(View, ListView).ObjectTypeInfo.Type, TryCast(e.ParameterCurrentValue, String))
e.ShowViewParameters.CreatedView = createdView
End Sub
Public Function CreateObjectViewBySubject(ByVal objectType As Type, ByVal subject As String) As View
Dim criteria As CriteriaOperator = CriteriaOperator.Parse($"Contains([Subject], '{subject}')")
Dim objectSpace As IObjectSpace = Application.CreateObjectSpace(objectType)
Dim obj As Object = objectSpace.FindObject(objectType, criteria)
Dim createdView As DetailView = Nothing
If obj IsNot Nothing Then
createdView = Application.CreateDetailView(objectSpace, obj)
End If
Return createdView
End Function
End Class
Unit Test Implementation
Let's create a test fixture class and a test method to implement our test:
C#using DevExpress.Data.Filtering;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;
using DevExpress.Xpo;
using MySolution.Module.BusinessObjects;
using MySolution.Module.Controllers;
using Moq;
using Moq.Protected;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MySolution.Tests {
[TestFixture]
public class FindBySubjectActionExecuteTest {
[Test]
public void FindSubjectTest() {
}
}
}
Visual BasicImports DevExpress.Data.Filtering
Imports DevExpress.ExpressApp
Imports DevExpress.ExpressApp.DC
Imports DevExpress.Xpo
Imports MainDemo.Module.BusinessObjects
Imports MainDemo.Module.Controllers
Imports Moq
Imports Moq.Protected
Imports NUnit.Framework
<TestFixture>
Public Class FindBySubjectActionExecuteTest
<Test>
Public Sub FindSubjectTest()
End Sub
End Class
Let's create several objects and use one of them as the target object that we need to find:
C#var objectType = typeof(DemoTask);
var demoTasks = new List<DemoTask> {
new DemoTask(Session.DefaultSession) {Subject = "Subject 1"},
new DemoTask(Session.DefaultSession) {Subject = "Subject 2"},
new DemoTask(Session.DefaultSession) {Subject = "Some subject 3"},
new DemoTask(Session.DefaultSession) {Subject = "Subject 4"},
};
const string targetSubject = "Some subject";
DemoTask targetDemoTask = demoTasks.First(dt => dt.Subject.Contains(targetSubject));
Visual BasicDim objectType = GetType(DemoTask)
Dim demoTasks = New List(Of DemoTask) From {
New DemoTask(Session.DefaultSession) With {
.Subject = "Subject 1"
},
New DemoTask(Session.DefaultSession) With {
.Subject = "Subject 2"
},
New DemoTask(Session.DefaultSession) With {
.Subject = "Some subject 3"
},
New DemoTask(Session.DefaultSession) With {
.Subject = "Subject 4"
}
}
Const targetSubject As String = "Some subject"
Dim targetDemoTask As DemoTask = demoTasks.First(Function(dt) dt.Subject.Contains(targetSubject))
Our code uses IObjectSpace.FindObject to find an object and create a mock for it:
C#var objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(o => o.FindObject(
It.Is<Type>(t => t == objectType),
It.Is<CriteriaOperator>(c => c.ToString() == $"Contains([Subject], '{targetSubject}')")
)).Returns(targetDemoTask);
Visual BasicDim objectSpaceMock = New Mock(Of IObjectSpace)()
objectSpaceMock.Setup(Function(o) o.FindObject(It.[Is](Of Type)(Function(t) t = objectType), It.[Is](Of CriteriaOperator)(Function(c) c.ToString() = $"Contains([Subject], '{targetSubject}')"))).Returns(targetDemoTask)
Then, we need an XafApplication instance. If we take a look at the XafApplication constructor (\DevExpress.ExpressApp\DevExpress.ExpressApp\XafApplication.cs), it calls the Initialize method.
C#public XafApplication(ITypesInfo typesInfo) {
Initialize(typesInfo);
TryEnableOptimizations();
}
public XafApplication()
: this(XafTypesInfo.Instance) {
}
The Initialize method checks if the passed typesInfo argument is not null.
C#private void Initialize(ITypesInfo typesInfo) {
Tick.In("new XafApplication()");
Guard.ArgumentNotNull(typesInfo, "typesInfo");
// ...
}
Thus, to mock XafApplication, we also need to provide it with a mocked ITypesInfo object. Let's do this.
C#var typeInfoMock = new Mock<ITypesInfo>();
var applicationMock = new Mock<XafApplication>(typeInfoMock.Object);
Visual BasicDim typeInfoMock = New Mock(Of ITypesInfo)()
Dim applicationMock = New Mock(Of XafApplication)(typeInfoMock.Object)
Now, we need to mock the XafApplication.CreateObjectSpace method. This method is not virtual.
C#public IObjectSpace CreateObjectSpace(Type objectType);
The Moq framework can't mock a not virtual class method. We can't mock the XafApplication.CreateObjectSpace method. To create our test, let's check how this method is implemented.
C#public IObjectSpace CreateObjectSpace(Type objectType) {
CheckCompatibility();
IObjectSpace objectSpace = CreateObjectSpaceCore(objectType);
if(objectSpace == null) {
objectSpace = CreateObjectSpaceCore(null);
}
if(!objectSpace.IsConnected) {
objectSpace.Connected += new EventHandler(ObjectSpace_Connected);
}
OnObjectSpaceCreated(objectSpace);
return objectSpace;
}
This method calls the CreateObjectSpaceCore method to create an IObjectSpace instance. The CreateObjectSpaceCore method is virtual:
C#protected virtual IObjectSpace CreateObjectSpaceCore(Type objectType);
We can mock the CreateObjectSpaceCore method instead of CreateObjectSpace.
In addition, the CreateObjectSpace method calls the CheckCompatibility method. We don't need its logic for our test. Let's prevent its logic from being executed. You could mock it, but this method is not virtual either.
C#public void CheckCompatibility() {
if(!IsCompatibilityChecked) {
// ...
}
}
However, the CheckCompatibility method checks the IsCompatibilityChecked property and does nothing if it's true. This property is virtual and we can mock it.
C#protected virtual bool IsCompatibilityChecked {
get { return isCompatibilityChecked; }
set { isCompatibilityChecked = value; }
}
Finally, we can create our XafApplication mock:
C#var typeInfoMock = new Mock<ITypesInfo>();
var applicationMock = new Mock<XafApplication>(typeInfoMock.Object);
applicationMock.Protected().Setup<IObjectSpace>("CreateObjectSpaceCore", ItExpr.Is<Type>(t => t == objectType)).Returns(objectSpaceMock.Object);
applicationMock.Protected().Setup<bool>("IsCompatibilityChecked").Returns(true);
Visual BasicDim typeInfoMock = New Mock(Of ITypesInfo)()
Dim applicationMock = New Mock(Of XafApplication)(typeInfoMock.Object)
applicationMock.Protected().Setup(Of IObjectSpace)("CreateObjectSpaceCore", ItExpr.[Is](Of Type)(Function(t) t = objectType)).Returns(objectSpaceMock.Object)
applicationMock.Protected().Setup(Of Boolean)("IsCompatibilityChecked").Returns(True)
Now, let's mock the XafApplication CreateDetailView method. Only this overload is virtual:
C#public virtual DetailView CreateDetailView(IObjectSpace objectSpace, string detailViewID, bool isRoot, object obj, bool isDelayedObjectLoading, IEnumerable objectsToPrefetch = null);
We need to mock this overload:
C#DetailView expectedDetailView = new DetailView(objectSpaceMock.Object, targetDemoTask, applicationMock.Object, true);
applicationMock.Setup(a => a.CreateDetailView(
It.Is<IObjectSpace>(o => o == objectSpaceMock.Object),
"",
true,
It.Is<object>(o => ReferenceEquals(o, targetDemoTask)),
false,
null)).Returns(expectedDetailView);
Visual BasicDim expectedDetailView As DetailView = New DetailView(objectSpaceMock.Object, targetDemoTask, applicationMock.Object, True)
applicationMock.Setup(Function(a) a.CreateDetailView(It.[Is](Of IObjectSpace)(Function(o) o Is objectSpaceMock.Object), "", True, It.[Is](Of Object)(Function(o) ReferenceEquals(o, targetDemoTask)), False, Nothing)).Returns(expectedDetailView)
Now, we need to create our Controller, assign the created XafApplication instance to it, call the CreateObjectViewBySubject method and check that our methods were called and the correct Detail View is returned:
C#FindBySubjectController controller = new FindBySubjectController();
controller.Application = applicationMock.Object;
var currentDetailView = controller.CreateObjectViewBySubject(objectType, targetSubject);
applicationMock.Verify();
Assert.That(currentDetailView, Is.EqualTo(expectedDetailView));
Visual BasicDim controller As FindBySubjectController = New FindBySubjectController()
controller.Application = applicationMock.Object
Dim currentDetailView = controller.CreateObjectViewBySubject(objectType, targetSubject)
applicationMock.Verify()
Assert.That(currentDetailView, [Is].EqualTo(expectedDetailView))
That appears to be all. Let's run our test. It fails with the following error:
CodeSystem.ArgumentException : An error with number 1021 has occurred.
Error message: The object that has been passed belongs to another ObjectSpace. This error may occur when you manipulate your objects via an ObjectSpace which these objects do not belong to. For instance, you may receive this error when using the XafApplication.CreateDetailView method in case you passed an object that was obtained from a different ObjectSpace than the ObjectSpace used as a parameter in this method. To correct this error, you should ensure that all the objects you manipulate belong to one ObjectSpace.
In most cases, to avoid this error it's sufficient to call the ObjectSpace.GetObject/GetObjectByKey methods to get a passed object in the target ObjectSpace.
If this doesn't help, please contact our Support Team at http://www.devexpress.com/Support/Center/
DevExpress.ExpressApp.Utils.Guard.CheckObjectFromObjectSpace(IObjectSpace objectSpace, Object obj)
DevExpress.ExpressApp.DetailView.set_CurrentObject(Object value)
DevExpress.ExpressApp.DetailView..ctor(IModelDetailView info, IObjectSpace objectSpace, Object obj, XafApplication application, Boolean isRoot)
DevExpress.ExpressApp.DetailView..ctor(IObjectSpace objectSpace, Object obj, XafApplication application, Boolean isRoot)
MySolution.Tests.FindBySubjectActionExecuteTest.FindSubjectTest() in FindBySubjectActionExecuteTest.cs
We can also debug our test and follow the How to obtain the exception's call stack article to catch this exception. The exception is raised in the Guard.CheckObjectFromObjectSpace method. Let's check its implementation (\DevExpress.ExpressApp\DevExpress.ExpressApp\Utils\Guard.cs):
C#public static void CheckObjectFromObjectSpace(IObjectSpace objectSpace, Object obj) {
if((obj != null) && !objectSpace.Contains(obj)) {
throw new ArgumentException(SystemExceptionLocalizer.GetExceptionMessage(ExceptionId.PassedObjectBelongsToAnotherObjectSpace));
}
}
Our test fails because the IObjectSpace.Contains method returns null. You may face such issues that relay in XAF internals when creating your test. This approach will help you find these issues and overcome them. In our case, we need to mock the IObjectSpace.Contains method to avoid the issue.
C#objectSpaceMock.Setup(o => o.Contains(It.Is<DemoTask>(dt => ReferenceEquals(dt, targetDemoTask)))).Returns(true);
Visual BasicobjectSpaceMock.Setup(Function(o) o.Contains(It.[Is](Of DemoTask)(Function(dt) ReferenceEquals(dt, targetDemoTask)))).Returns(True)
After this, our test passes. The entire test method code is the following:
C#[Test]
public void FindSubjectTest() {
var objectType = typeof(DemoTask);
var demoTasks = new List<DemoTask> {
new DemoTask(Session.DefaultSession) {Subject = "Subject 1"},
new DemoTask(Session.DefaultSession) {Subject = "Subject 2"},
new DemoTask(Session.DefaultSession) {Subject = "Some subject 3"},
new DemoTask(Session.DefaultSession) {Subject = "Subject 4"},
};
const string targetSubject = "Some subject";
DemoTask targetDemoTask = demoTasks.First(dt => dt.Subject.Contains(targetSubject));
var objectSpaceMock = new Mock<IObjectSpace>();
objectSpaceMock.Setup(o => o.FindObject(
It.Is<Type>(t => t == objectType),
It.Is<CriteriaOperator>(c => c.ToString() == $"Contains([Subject], '{targetSubject}')")
)).Returns(targetDemoTask);
objectSpaceMock.Setup(o => o.Contains(It.Is<DemoTask>(dt => ReferenceEquals(dt, targetDemoTask)))).Returns(true);
var typeInfoMock = new Mock<ITypesInfo>();
var applicationMock = new Mock<XafApplication>(typeInfoMock.Object);
applicationMock.Protected().Setup<IObjectSpace>("CreateObjectSpaceCore", ItExpr.Is<Type>(t => t == objectType)).Returns(objectSpaceMock.Object);
applicationMock.Protected().Setup<bool>("IsCompatibilityChecked").Returns(true);
DetailView expectedDetailView = new DetailView(objectSpaceMock.Object, targetDemoTask, applicationMock.Object, true);
applicationMock.Setup(a => a.CreateDetailView(
It.Is<IObjectSpace>(o => o == objectSpaceMock.Object),
"",
true,
It.Is<object>(o => ReferenceEquals(o, targetDemoTask)),
false,
null)).Returns(expectedDetailView);
FindBySubjectController controller = new FindBySubjectController();
controller.Application = applicationMock.Object;
var currentDetailView = controller.CreateObjectViewBySubject(objectType, targetSubject);
applicationMock.Verify();
Assert.That(currentDetailView, Is.EqualTo(expectedDetailView));
}
Visual Basic<Test>
Public Sub FindSubjectTest()
Dim objectType = GetType(DemoTask)
Dim demoTasks = New List(Of DemoTask) From {
New DemoTask(Session.DefaultSession) With {
.Subject = "Subject 1"
},
New DemoTask(Session.DefaultSession) With {
.Subject = "Subject 2"
},
New DemoTask(Session.DefaultSession) With {
.Subject = "Some subject 3"
},
New DemoTask(Session.DefaultSession) With {
.Subject = "Subject 4"
}
}
Const targetSubject As String = "Some subject"
Dim targetDemoTask As DemoTask = demoTasks.First(Function(dt) dt.Subject.Contains(targetSubject))
Dim objectSpaceMock = New Mock(Of IObjectSpace)()
objectSpaceMock.Setup(Function(o) o.FindObject(It.[Is](Of Type)(Function(t) t = objectType), It.[Is](Of CriteriaOperator)(Function(c) c.ToString() = $"Contains([Subject], '{targetSubject}')"))).Returns(targetDemoTask)
objectSpaceMock.Setup(Function(o) o.Contains(It.[Is](Of DemoTask)(Function(dt) ReferenceEquals(dt, targetDemoTask)))).Returns(True)
Dim typeInfoMock = New Mock(Of ITypesInfo)()
Dim applicationMock = New Mock(Of XafApplication)(typeInfoMock.Object)
applicationMock.Protected().Setup(Of IObjectSpace)("CreateObjectSpaceCore", ItExpr.[Is](Of Type)(Function(t) t = objectType)).Returns(objectSpaceMock.Object)
applicationMock.Protected().Setup(Of Boolean)("IsCompatibilityChecked").Returns(True)
Dim expectedDetailView As DetailView = New DetailView(objectSpaceMock.Object, targetDemoTask, applicationMock.Object, True)
applicationMock.Setup(Function(a) a.CreateDetailView(It.[Is](Of IObjectSpace)(Function(o) o Is objectSpaceMock.Object), "", True, It.[Is](Of Object)(Function(o) ReferenceEquals(o, targetDemoTask)), False, Nothing)).Returns(expectedDetailView)
Dim controller As FindBySubjectController = New FindBySubjectController()
controller.Application = applicationMock.Object
Dim currentDetailView = controller.CreateObjectViewBySubject(objectType, targetSubject)
applicationMock.Verify()
Assert.That(currentDetailView, [Is].EqualTo(expectedDetailView))
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 (current)
- 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
- 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