When writing integration tests for your CSLA domain some choices about how to deal with child objects need to be made. For example, do you test all your child objects through their parents? Do you attempt to isolate the child by somehow mocking the parent? Do you just ignore child objects all together? Or if you are like most places you just do not write integration tests or unit tests (Yes it is true). I lean towards testing all objects in isolation. If you don’t isolate an object when testing it you really are not getting the feedback you need to understand what problems may exists.
So how do you isolate the data portal methods in a child CSLA object?
The approach I generally take is to create a fake editable root object. This object is a BusinessBase object that leverages method dependency injection enabling you to inject child objects (and parent objects) into it. You can then call save on the fake root object allowing the CSLA framework to delegate the calls to the child objects as it normally would. Let’s take a look at how that might look in a test.
/// <summary> /// Tests ChildDataPortal.Insert /// </summary> [TestMethod] public void TestCreateAndSaveNewChild() { //Create instance of the Object Under Test Department department = Department.CreateAsChild("TEST"); department.Description = "Test Department"; //Create instance of FakeEditableRoot FakeEditableRoot fake = FakeEditableRoot.Create(); fake.InjectChild(department); fake = fake.Save(); //Get the saved child department = fake.ChildObject as Department; //Assert Assert.IsTrue(Department.Exists("TEST")); Assert.IsTrue(department.Id == "TEST"); Assert.IsTrue(department.Description == "Test Department"); }
So what does FakeEditableRoot look like? Sadly it is not very interesting but take a look if you like.
/// <summary> /// Fake CSLA Root. Use this to test child object and their data portal operations /// </summary> [Serializable] public sealed class FakeEditableRoot : MyBusinessBase<FakeEditableRoot> { /// <summary> /// The child object under test /// </summary> private static PropertyInfo<BusinessBase> ChildObjectProperty = RegisterProperty<BusinessBase>(typeof(FakeEditableRoot), new PropertyInfo<BusinessBase>("ChildObject")); /// <summary> /// Allows you to determine what 'Parent' object is passed to the child data portal operations /// </summary> private static PropertyInfo<BusinessBase> ParentObjectProperty = RegisterProperty<BusinessBase>(typeof(FakeEditableRoot), new PropertyInfo<BusinessBase>("ParentObject")); /// <summary> /// Used for testing child collections /// </summary> private static PropertyInfo<IExtendedBindingList> ChildCollectionProperty = RegisterProperty<IExtendedBindingList>(typeof(FakeEditableRoot), new PropertyInfo<IExtendedBindingList>("ChildCollectionObject")); #region Factory Methods /// <summary> /// Factory Method used to create a new instance of FakeEditableRoot /// </summary> /// <returns>New instance of FakeEditableRoot</returns> public static FakeEditableRoot Create() { return new FakeEditableRoot(); } /// <summary> /// Sets the parent object to be passed to the data portal operations. /// </summary> /// <param name="parent">The parent.</param> public void InjectParent(BusinessBase parent) { ParentObject = parent; } /// <summary> /// Injects the child collection to test. /// </summary> /// <param name="childCollection">The child collection.</param> public void InjectChildCollection(IExtendedBindingList childCollection) { ChildCollection = childCollection; } /// <summary> /// Injects the child object to test. /// </summary> /// <param name="child">The child.</param> public void InjectChild(BusinessBase child) { ChildObject = child; } /// <summary> /// User to simulate a fetch /// </summary> /// <returns></returns> public static FakeEditableRoot GetById() { return DataPortal.Fetch<FakeEditableRoot>(null); } #endregion #region Constructors /// <summary> /// Initializes a new instance of the <see cref="FakeEditableRoot"/> class. /// </summary> private FakeEditableRoot() { } /// <summary> /// Private constructor to force the Factory Method Pattern. /// </summary> /// <param name="childObject">The child object.</param> private FakeEditableRoot(BusinessBase childObject) { ChildObject = childObject; } /// <summary> /// Private constructor to force the Factory Method Pattern. /// </summary> /// <param name="childObject">The child object.</param> /// <param name="parentObject">The parent object.</param> private FakeEditableRoot(BusinessBase childObject, BusinessBase parentObject) { ChildObject = childObject; ParentObject = parentObject; } #endregion #region Public Properties /// <summary> /// Gets or sets the child object. /// </summary> /// <value>The child object.</value> public BusinessBase ChildObject { get { return GetProperty<BusinessBase>(ChildObjectProperty); } private set { SetProperty<BusinessBase>(ChildObjectProperty, value); } } /// <summary> /// Gets or sets the child object. /// </summary> /// <value>The child object.</value> public BusinessBase ParentObject { get { return GetProperty<BusinessBase>(ParentObjectProperty); } private set { SetProperty<BusinessBase>(ParentObjectProperty, value); } } /// <summary> /// Gets or sets the child collection object. /// </summary> /// <value>The child collection object.</value> public IExtendedBindingList ChildCollection { get { return GetProperty<IExtendedBindingList>(ChildCollectionProperty); } private set { SetProperty<IExtendedBindingList>(ChildCollectionProperty, value); } } #endregion #region Data Access - Fetch /// <summary> /// Datas the portal_ fetch. /// </summary> /// <param name="criteria">The criteria.</param> protected override void DataPortal_Fetch(object criteria) { } #endregion #region Data Access - Insert /// <summary> /// Save the current instance /// </summary> protected override void DataPortal_Insert() { //Get connection and command Database db = new OracleDatabase(ManagedConnectionString.Instance.ConnectionString); using (DbConnection connection = db.CreateConnection()) { connection.Open(); using (DbTransaction transaction = connection.BeginTransaction()) { try { if (ParentObject != null) { FieldManager.UpdateChildren(ParentObject, transaction); } else { FieldManager.UpdateChildren(this, transaction); } //Commit Transaction transaction.Commit(); } catch { transaction.Rollback(); throw; } } } } #endregion #region Data Access - Update /// <summary> /// Save the current instance /// </summary> protected override void DataPortal_Update() { //Get connection and command Database db = new OracleDatabase(ManagedConnectionString.Instance.ConnectionString); using (DbConnection connection = db.CreateConnection()) { connection.Open(); using (DbTransaction transaction = connection.BeginTransaction()) { try { //Update Children if (ParentObject != null) { FieldManager.UpdateChildren(ParentObject, transaction); } else { FieldManager.UpdateChildren(this, transaction); } //Commit Transaction transaction.Commit(); } catch { transaction.Rollback(); throw; } } } } #endregion }
Enjoy!