KB Article T853287
Visible to All Users

How to unit test New Action's custom business logic based on parent and nested Views

Test Scenario
We have the following Controller that copies properties of a current object to a new object. Our task is to test if the CreateLinkedSaleBase method correctly copies properties from one object to another. We don't need to test if the NewObjectViewController ObjectCreated event is raised. This is XAF's responsibility and this should be tested by functional tests.

C#
public class CreateLinkedSaleBaseDescendantController : ViewController { protected override void OnActivated() { base.OnActivated(); NewObjectViewController standardController = Frame.GetController<NewObjectViewController>(); standardController.ObjectCreated += new EventHandler<ObjectCreatedEventArgs>(CreateLinkedSaleBaseDescendantController_ObjectCreated); } public void CreateLinkedSaleBaseDescendantController_ObjectCreated(object sender, ObjectCreatedEventArgs e) { SaleBase createdObject = e.CreatedObject as SaleBase; IObjectSpace objectSpace = e.ObjectSpace; CreateLinkedSaleBase(objectSpace, createdObject); } public void CreateLinkedSaleBase(IObjectSpace objectSpace, SaleBase createdObject) { NestedFrame nestedFrame = Frame as NestedFrame; if (nestedFrame != null) { SaleBase parentObject = objectSpace.GetObject(nestedFrame.ViewItem.CurrentObject as SaleBase); if (createdObject != null) { parentObject?.Copy(createdObject); } } } }
Visual Basic
Public Class CreateLinkedSaleBaseDescendantController Inherits ViewController Protected Overrides Sub OnActivated() MyBase.OnActivated() Dim standardController = Frame.GetController(Of NewObjectViewController)() AddHandler standardController.ObjectCreated, AddressOf CreateLinkedSaleBaseDescendantController_ObjectCreated End Sub Public Sub CreateLinkedSaleBaseDescendantController_ObjectCreated(ByVal sender As Object, ByVal e As ObjectCreatedEventArgs) Dim createdObject As SaleBase = TryCast(e.CreatedObject, SaleBase) Dim objectSpace As IObjectSpace = e.ObjectSpace CreateLinkedSaleBase(objectSpace, createdObject) End Sub Public Sub CreateLinkedSaleBase(ByVal objectSpace As IObjectSpace, ByVal createdObject As SaleBase) Dim nestedFrame As NestedFrame = TryCast(Frame, NestedFrame) If nestedFrame IsNot Nothing Then Dim parentObject As SaleBase = objectSpace.GetObject(TryCast(nestedFrame.ViewItem.CurrentObject, SaleBase)) If createdObject IsNot Nothing Then parentObject?.Copy(createdObject) End If End If End Sub End Class

The essential SaleBase class implementation part is:

C#
public abstract class SaleBase : IObjectSpaceLink, IXafEntityObject, INotifyPropertyChanged { [DevExpress.ExpressApp.DC.Aggregated] [InverseProperty(nameof(SaleItem.SaleBase))] public virtual IList<SaleItem> SaleItems { get; set; } public decimal Discount { get { return discount; } set { discount = value; } } public void Copy(SaleBase target) { target.Discount = Discount; foreach(SaleItem saleItem in SaleItems) { SaleItem saleItemNew = ObjectSpace.CreateObject<SaleItem>(); saleItemNew.Discount = saleItem.Discount; saleItemNew.Product = saleItem.Product; saleItemNew.Quantity = saleItem.Quantity; saleItemNew.UpdateAmount(); target.SaleItems.Add(saleItemNew); } } }
Visual Basic
Public MustInherit Class SaleBase Implements IObjectSpaceLink, IXafEntityObject, INotifyPropertyChanged <DevExpress.ExpressApp.DC.Aggregated> <InverseProperty(NameOf(SaleItem.SaleBase))> Public Overridable Property SaleItems As IList(Of SaleItem) Public Property Discount As Decimal Get Return fDiscount End Get Set(ByVal value As Decimal) fDiscount = value End Set End Property Public Sub Copy(ByVal target As SaleBase) target.Discount = Discount For Each saleItem As SaleItem In SaleItems Dim saleItemNew As SaleItem = ObjectSpace.CreateObject(Of SaleItem)() saleItemNew.Discount = saleItem.Discount saleItemNew.Product = saleItem.Product saleItemNew.Quantity = saleItem.Quantity saleItemNew.UpdateAmount() target.SaleItems.Add(saleItemNew) Next End Sub End Class

Unit Test Implementation
Let's create a test fixture class and a test method:

C#
using System.Collections.Generic; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Editors; using Moq; using NUnit.Framework; using MySolution.Module.Controllers; using MySolution.Module.Data; namespace MySolution.Tests { [TestFixture] public class CreateLinkedSaleBaseDescendantControllerTest { [Test] public void Test() { } } }
Visual Basic
Imports DevExpress.ExpressApp Imports DevExpress.ExpressApp.Editors Imports Moq Imports NUnit.Framework Imports MySolution.Module <TestFixture> Public Class CreateLinkedSaleBaseDescendantControllerTest <Test> Public Sub Test() End Sub End Class

For our code, we need two SaleBase objects: one that will serve as a current (parent) object and another one as a newly created object. The SaleBase class is abstract. We will use mocks to create its instances. In addition, you need to mock the SaleItems property to return an empty SaleItems list to avoid a NullReference exception in the Copy method. Let's do this.

C#
Mock<SaleBase> createdObjectMock = new Mock<SaleBase>(); Mock<SaleBase> parentObjectMock = new Mock<SaleBase>(); parentObjectMock.Object.Discount = 123; parentObjectMock.SetupGet(m => m.SaleItems).Returns(new List<SaleItem>());
Visual Basic
Dim createdObjectMock As Mock(Of SaleBase) = New Mock(Of SaleBase)() Dim parentObjectMock As Mock(Of SaleBase) = New Mock(Of SaleBase)() parentObjectMock.Object.Discount = 123 parentObjectMock.SetupGet(Function(m) m.SaleItems).Returns(New List(Of SaleItem)())

Then, we need to mock an IObjectSpace object and its GetObject method:

C#
Mock<IObjectSpace> objectSpaceMock = new Mock<IObjectSpace>(); objectSpaceMock.Setup(m => m.GetObject(It.Is<SaleBase>(s => s == parentObjectMock.Object))).Returns(parentObjectMock.Object);
Visual Basic
Dim objectSpaceMock As Mock(Of IObjectSpace) = New Mock(Of IObjectSpace)() objectSpaceMock.Setup(Function(m) m.GetObject(It.[Is](Of SaleBase)(Function(s) s Is parentObjectMock.Object))).Returns(parentObjectMock.Object)

Our logic uses NestedFrame, its ViewItem and a current object in this ViewItem. ViewItem is an abstract class. We will use mocks to create its instance. The ViewItem constructor is:

C#
public ViewItem(Type objectType, string id);

Our business logic does not use the ViewItem Id, we can pass any string to the constructor. The objectType parameter will be the SaleBase type. The NestedFrame class is implemented as follows:

C#
public class NestedFrame : Frame { public NestedFrame(XafApplication application, TemplateContext context, ViewItem viewItem, ICollection<Controller> controllers); public ViewItem ViewItem { get; } }

Our code does not use XafApplication and TemplateContext instances. We can pass nulls for them to create a NestedFrame instance.

C#
Mock<ViewItem> viewItemMock = new Mock<ViewItem>(typeof(SaleBase), "testID"); viewItemMock.Object.CurrentObject = parentObjectMock.Object; NestedFrame nestedFrame = new NestedFrame(null, null, viewItemMock.Object, new List<Controller>());
Visual Basic
Dim viewItemMock As Mock(Of ViewItem) = New Mock(Of ViewItem)(GetType(SaleBase), "testID") viewItemMock.Object.CurrentObject = parentObjectMock.Object Dim nestedFrame As NestedFrame = New NestedFrame(Nothing, Nothing, viewItemMock.Object, New List(Of Controller)())

Now, we can create our CreateLinkedSaleBaseDescendantController instance, register it in the created NestedFrame and its CreateLinkedSaleBase method:

C#
CreateLinkedSaleBaseDescendantController controller = new CreateLinkedSaleBaseDescendantController(); nestedFrame.RegisterController(controller); controller.CreateLinkedSaleBase(objectSpaceMock.Object, createdObjectMock.Object);
Visual Basic
Dim controller As CreateLinkedSaleBaseDescendantController = New CreateLinkedSaleBaseDescendantController() nestedFrame.RegisterController(controller) controller.CreateLinkedSaleBase(objectSpaceMock.Object, createdObjectMock.Object)

Let's verify that the SaleBase Copy method was called and the Discount property of the created object is 123:

C#
parentObjectMock.Verify(); Assert.That(123, Is.EqualTo(createdObjectMock.Object.Discount));
Visual Basic
parentObjectMock.Verify() Assert.That(123, [Is].EqualTo(createdObjectMock.Object.Discount))

That's it. The entire test method will be:

C#
[Test] public void Test() { Mock<SaleBase> createdObjectMock = new Mock<SaleBase>(); Mock<SaleBase> parentObjectMock = new Mock<SaleBase>(); parentObjectMock.Object.Discount = 123; parentObjectMock.SetupGet(m => m.SaleItems).Returns(new List<SaleItem>()); Mock<IObjectSpace> objectSpaceMock = new Mock<IObjectSpace>(); objectSpaceMock.Setup(m => m.GetObject(It.Is<SaleBase>(s => s == parentObjectMock.Object))).Returns(parentObjectMock.Object); Mock<ViewItem> viewItemMock = new Mock<ViewItem>(typeof(SaleBase), "testID"); viewItemMock.Object.CurrentObject = parentObjectMock.Object; NestedFrame nestedFrame = new NestedFrame(null, null, viewItemMock.Object, new List<Controller>()); CreateLinkedSaleBaseDescendantController controller = new CreateLinkedSaleBaseDescendantController(); nestedFrame.RegisterController(controller); controller.CreateLinkedSaleBase(objectSpaceMock.Object, createdObjectMock.Object); parentObjectMock.Verify(); Assert.That(123, Is.EqualTo(createdObjectMock.Object.Discount)); }
Visual Basic
<Test> Public Sub Test() Dim createdObjectMock As Mock(Of SaleBase) = New Mock(Of SaleBase)() Dim parentObjectMock As Mock(Of SaleBase) = New Mock(Of SaleBase)() parentObjectMock.Object.Discount = 123 parentObjectMock.SetupGet(Function(m) m.SaleItems).Returns(New List(Of SaleItem)()) Dim objectSpaceMock As Mock(Of IObjectSpace) = New Mock(Of IObjectSpace)() objectSpaceMock.Setup(Function(m) m.GetObject(It.[Is](Of SaleBase)(Function(s) s Is parentObjectMock.Object))).Returns(parentObjectMock.Object) Dim viewItemMock As Mock(Of ViewItem) = New Mock(Of ViewItem)(GetType(SaleBase), "testID") viewItemMock.Object.CurrentObject = parentObjectMock.Object Dim nestedFrame As NestedFrame = New NestedFrame(Nothing, Nothing, viewItemMock.Object, New List(Of Controller)()) Dim controller As CreateLinkedSaleBaseDescendantController = New CreateLinkedSaleBaseDescendantController() nestedFrame.RegisterController(controller) controller.CreateLinkedSaleBase(objectSpaceMock.Object, createdObjectMock.Object) parentObjectMock.Verify() Assert.That(123, [Is].EqualTo(createdObjectMock.Object.Discount)) 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 (current)
  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)
  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.