Tag Archives: Business Rules

Unit Test CSLA 4 BusinessRules

Unit tests in CSLA 4 is now classes and most developers want to create unit tests to verify the correct behavior and result.
In this article I will show you a suggested way to create these tests.

Basic rule knowledge

Let’s start with how rules is registered in CSLA:

  1. <bo>.AddBusinessRules is only called once and register ALL rules for that object type.
  2. A rule instance should be viewed as a Singelton or Shared object. You cannot store any value elated to the execution (data values) in member variables.

The execution of the rule follow these steps:

  1. The RuleEngine will create an instance of a RuleContext with the actual rule
  2. and if InputProperties have entries copy values from BO to <context>.InputPropertyValues
  3. set <context>.Target property to the <bo> instance (with exception if IsAsync = true and ProvideTargetWhenAsync = false).
  4. call the <rule>.Execute method with <context> as parameter.
  5. When rule has completed call a CompleteHandler for both synchronous and async rules.

The rule engine will call the completed handler automatically for syncronous rules but for async rules your code is responsible for calling <context>.Complete in ALL execution paths.
The <rule>.Execute method is always called inline and when IsAsync = true the assumption is that your code in Execute will do an asyncronous call an in order to notify the rule engine when the rule is completed your code must call <rule>.Complete.

Then take a look at the <rule>.Execute method.

A: Your rule does NOT use <context>.Target and has no relationship to the actual BO.
These rules only interact with the RuleContext and use <context>.InputPropertyValues for input values and may call <context>.AddOutValue or context.AddXYZResult.

The output from these rules is in

  • <context>.Results
  • <context>.OuputPropertyValues

B: Your uses uses <context>.Target and the support methods in BusinessRule base class for ReadProperty or LoadProperty.
These rules interact with the BO and uses methods in BusinessRule base class that expects an actual CSLA BO to be present (implements Csla.Core.IManageProperties).

The output of a rule can be

The output from these rules is in

  • <context>.Results
  • <context>.OuputPropertyValues
  • directly updated properties in the context.Target or child object from calling <rule>.LoadProperty

RuleContext support for result

context.Results is a list of RuleResult that yon can add test for. <context>.AddXYZResult will all add a RuleResult to context.Results.
context.OutputPropertyValues is a dictionary of IPropertyInfo and values to be set in the bo.

So we should be able to create unit tests for both

  • synchreonous rules and async rules
  • rules that only interacts with the rule context.
  • rules that interacts with the context.Target

Support methods in CSLA

RuleContext has a constructor specifically designed for use in unit tests:

public RuleContext(Action<RuleContext> completeHandler, IBusinessRule rule, object target, Dictionary<Csla.Core.IPropertyInfo, object> inputPropertyValues)

that allows the test code to specify the

  • completeHandler for callback when rule has completed.
  • the actual business ruleyou want to test
  • the target object (may be null if your rule does not use the context.Target)
  • a dictionary of input property values

Support class BusinessRuleTest.cs in RuleTutorial samples download

adds more support for tests and will allow you to write the exact same test code whether the rule is syncronous or not.
This is done by adding a CompleteEeventHandler that will wait for up to 3 seconds on an async rule before calling <context>.Complete.

public void InitializeTest(IBusinessRule rule, object target)
public void ExecuteRule()
public void ExecuteRule(Dictionary<IPropertyInfo, object> inputPropertyValues)
public T GetOutputPropertyValue<T>(PropertyInfo<T> propertyInfo)

The ExeuteRule method will copy values from target object to input property values and then call rule.Execute
The ExecuteRule with InputPropertyValues dictionary will just set thes into the context and call rule.Execute.

Then to the unit tests

My proposed solution for creating unit tests uses the constructor in RuleContext, the BusinessRuleTest base class and creating fake business objects for the tests.

I like the testing approach from Phil Haack and Brad Wilson so these sample tests use a separate class for each method.
Read more here: http://haacked.com/archive/2012/01/01/structuring-unit-tests.aspx

Testing a ToUpper transformation rule that makes sure a property is always uppercase.

This rule only interacts with the <context>.InputPropertyValues and <context>.OuputPropertyValues

My fake root object:

  [Serializable]
  public class RootFake : BusinessBase<RootFake>
  {
    public static readonly PropertyInfo<int> CustomerIdProperty = RegisterProperty<int>(c => c.CustomerId);
    public int CustomerId
    {
      get { return GetProperty(CustomerIdProperty); }
      set { SetProperty(CustomerIdProperty, value); }
    }

    public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(c => c.Name);
    public string Name
    {
      get { return GetProperty(NameProperty); }
      set { SetProperty(NameProperty, value); }
    }
  }

The the test class for constructor:

    [TestClass()]
    public class TheCtor
    {
      private IBusinessRule Rule;
      [TestInitialize]
      public void InitTests()
      {
        Rule = new ToUpper(RootFake.NameProperty);
      }

      [TestMethod]
      public void IsSync()
      {
        Assert.IsFalse(Rule.IsAsync);
      }

      [TestMethod]
      public void HasPrimaryProperty()
      {
        Assert.IsNotNull(Rule.PrimaryProperty);
        Assert.AreEqual(RootFake.NameProperty, Rule.PrimaryProperty);
      }

      [TestMethod]
      public void HasInputProperties()
      {
        Assert.IsTrue(Rule.InputProperties.Contains(RootFake.NameProperty));
      }

      [TestMethod]
      public void HasAffectedProperties()
      {
        Assert.IsTrue(Rule.AffectedProperties.Contains(RootFake.NameProperty));
      }
    }

Then testing the Execute method with supplied InputPropertyValues:

    [TestClass()]
    public class TheExecuteMethod : BusinessRuleTest
    {
      [TestInitialize]
      public void InitTests()
      {
        var rule = new ToUpper(RootFake.NameProperty);
        InitializeTest(rule, null);
      }

      [TestMethod]
      public void MustSetOutputPropertyToUpper()
      {
        var expected = "CSLA ROCKS";
        ExecuteRule(new Dictionary<IPropertyInfo, object>() {{RootFake.NameProperty, "csla rocks"}});
        Assert.IsTrue(GetOutputPropertyValue(RootFake.NameProperty) == expected);
      }
    }

Then testing the Execute method with a fake object to supply the property value:

    [TestClass()]
    public class TheExecuteMethodAlt : BusinessRuleTest
    {
      [TestInitialize]
      public void InitTests()
      {
        var rule = new ToUpper(RootFake.NameProperty);
        var root = new RootFake();
        root.Name = "csla rocks";
        InitializeTest(rule, root);
      }

      [TestMethod]
      public void MustSetOutputPropertyToUpper()
      {
        var expected = "CSLA ROCKS";
        ExecuteRule();
        Assert.IsTrue(GetOutputPropertyValue(RootFake.NameProperty) == expected);
      }
    }

Summary

I hope this gave you a better understanding of how Business Rules work and what to test.

You will find more unit test samples in the Net\cs\RuleTutorial sample available at http://www.lhotka.net/cslanet/download.aspx and if you have questions please post on the CSLA,NET Forum at http://forums.lhotka.net

Advertisements