KB Article T884471
Visible to All Users

How to unit test custom logic in XAF/XPO business classes

Test Scenario
Let's imagine that we have a persistent class (Master) that has a non-associated reference property (Reference) and two associated collection properties for one-to-many (Children1) and many-to-many (Children2) relationships with different child classes.

C#
public class Master : XPObject { public Master(Session session) : base(session) { } Reference reference; string name; public string Name { get => name; set => SetPropertyValue(nameof(Name), ref name, value); } public Reference Reference { get => reference; set => SetPropertyValue(nameof(Reference), ref reference, value); } [Association] public XPCollection<Child1> Children1 { get { return GetCollection<Child1>(nameof(Children1)); } } [Association] public XPCollection<Child2> Children2 { get { return GetCollection<Child2>(nameof(Children2)); } } } public class Child1 : XPObject { public Child1(Session session) : base(session) { } Master master; string name; public string Name { get => name; set => SetPropertyValue(nameof(Name), ref name, value); } [Association] public Master Master { get => master; set => SetPropertyValue(nameof(Master), ref master, value); } } public class Child2 : XPObject { public Child2(Session session) : base(session) { } string name; public string Name { get => name; set => SetPropertyValue(nameof(Name), ref name, value); } [Association] public XPCollection<Master> Masters { get { return GetCollection<Master>(nameof(Masters)); } } } public class Reference : XPObject { public Reference(Session session) : base(session) { } bool isDefault; string name; public string Name { get => name; set => SetPropertyValue(nameof(Name), ref name, value); } public bool IsDefault { get => isDefault; set => SetPropertyValue(nameof(IsDefault), ref isDefault, value); } }
Visual Basic
Public Class Master Inherits XPObject Public Sub New(ByVal session As Session) MyBase.New(session) End Sub Private _reference As Reference Private _name As String Property Name As String Get Return _name End Get Set(ByVal Value As String) SetPropertyValue(NameOf(Name), _name, Value) End Set End Property Property Reference As Reference Get Return _reference End Get Set(ByVal Value As Reference) SetPropertyValue(NameOf(Reference), _reference, Value) End Set End Property <Association> Public ReadOnly Property Children1() As XPCollection(Of Child1) Get Return GetCollection(Of Child1)(NameOf(Children1)) End Get End Property <Association> Public ReadOnly Property Children2() As XPCollection(Of Child2) Get Return GetCollection(Of Child2)(NameOf(Children2)) End Get End Property End Class Public Class Child1 Inherits XPObject Public Sub New(ByVal session As Session) MyBase.New(session) End Sub Private _master As Master Private _name As String Property Name As String Get Return _name End Get Set(ByVal Value As String) SetPropertyValue(NameOf(Name), _name, Value) End Set End Property <Association> Property Master() As Master Get Return _master End Get Set(ByVal Value As Master) SetPropertyValue(NameOf(Master), _master, Value) End Set End Property End Class Public Class Child2 Inherits XPObject Public Sub New(ByVal session As Session) MyBase.New(session) End Sub Private _name As String Property Name As String Get Return _name End Get Set(ByVal Value As String) SetPropertyValue(NameOf(Name), _name, Value) End Set End Property <Association> Public ReadOnly Property Masters() As XPCollection(Of Master) Get Return GetCollection(Of Master)(NameOf(Masters)) End Get End Property End Class Public Class Reference Inherits XPObject Public Sub New(ByVal session As Session) MyBase.New(session) End Sub Private _isDefault As Boolean Private _name As String Property Name As String Get Return _name End Get Set(ByVal Value As String) SetPropertyValue(NameOf(Name), _name, Value) End Set End Property Property IsDefault As Boolean Get Return _isDefault End Get Set(ByVal Value As Boolean) SetPropertyValue(NameOf(IsDefault), _isDefault, Value) End Set End Property End Class

We also implemented business logic for two scenarios that we want to unit test:

Scenario 1. Ensure that the Reference property is correctly initialized when a new Master object is created.

C#
public class Master : XPObject { public override void AfterConstruction() { base.AfterConstruction(); Reference = Session.FindObject<Reference>(new BinaryOperator(nameof(Reference.IsDefault), true)); } //... }
Visual Basic
Public Class Master Inherits XPObject Public Overrides Sub AfterConstruction() MyBase.AfterConstruction() Reference = Session.FindObject(Of Reference)(New BinaryOperator(NameOf(Reference.IsDefault), True)) End Sub '... End Class

Scenario 2. Ensure that the associated collections of the Master object are correctly intialized

C#
// This code can be in any place of your application. Reference reference1 = new Reference(session); reference1.IsDefault = true; session.CommitChanges(); Master master = new Master(session); Child1 child11 = new Child1(session); child11.Name = "Child1"; child11.Master = master; Child2 child21 = new Child2(session); child21.Name = "Child2"; child21.Masters.Add(master);
Visual Basic
'This code can be in any place of your application. Dim reference1 As Reference = New Reference(session) reference1.IsDefault = True session.CommitChanges() Dim master As Master = New Master(session) Dim child11 As Child1 = New Child1(session) child11.Name = "Child1" child11.Master = master Dim child21 As Child2 = New Child2(session) child21.Name = "Child2" child21.Masters.Add(master)

Unit Test Implementation
We want to test that the IsDefault property of the Reference object is true, that the Children1 collection contains the child11 object, and that the number of objects in the Children2 collection is 1.

C#
Assert.AreEqual(master.Reference.IsDefault, true); Assert.Contains(child11, master.Children1); Assert.AreEqual(master.Children2.Count, 1);
Visual Basic
Assert.AreEqual(master.Reference.IsDefault, True) Assert.Contains(child11, master.Children1) Assert.AreEqual(master.Children2.Count, 1)

Although it is possible to mock database access with Session and other XPO APIs as we demonstrated in other examples, this approach is cumbersome and error-prone when it comes to testing persistent objects.

For testing and demo purposes, XPO provides a special lightweight data store that keeps data in the memory - InMemoryDataStore. With InMemoryDataStore, you also won't need to create and drop a real database.

To make tests independent from data created by other tests and put logic to create a data store and a Session in a single place, it's recommended to create them in the SetUp method and dispose of them in the TearDown method. You may also consider avoiding the use of the default Session and set it to null in the OneTimeSetUp method.

C#
IDataLayer dataLayer; UnitOfWork session; [OneTimeSetUp] public void OneTimeSetUp() { XpoDefault.Session = null; } [SetUp] public void SetUp() { dataLayer = new SimpleDataLayer(new InMemoryDataStore(AutoCreateOption.DatabaseAndSchema, false)); session = new UnitOfWork(dataLayer); } [TearDown] public void TearDown() { session.Dispose(); dataLayer.Dispose(); }
Visual Basic
Private dataLayer As IDataLayer Private session As UnitOfWork <OneTimeSetUp> Public Sub OneTimeSetUp() XpoDefault.Session = Nothing End Sub <SetUp> Public Sub SetUp() dataLayer = New SimpleDataLayer(New InMemoryDataStore(AutoCreateOption.DatabaseAndSchema, False)) session = New UnitOfWork(dataLayer) End Sub <TearDown> Public Sub TearDown() session.Dispose() dataLayer.Dispose() End Sub

With this XPO setup, you can add test methods that access the 'session' variable and leave your business logic unchanged.

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)
  10. How to unit test localized strings from CaptionHelper (Approach 2)
  11. How to unit test custom logic in XAF/XPO business classes (current)

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.