Skip to Content
Author's profile photo Former Member

Intro to Test Driven Development Starring the Chain of Responsibility Design Pattern

As part of my goals this year, I wanted to have discussions on Test Driven Development (TDD) and Design Patterns both of which go hand in hand with the Agile methodology our current teams have recently adopted.    The following is a simple scenario that describes the problem at hand with requirements that are akin to a ‘user story’ which continue to grow.  It would probably be a good idea to review Junit 4 prior to this exercise.
Let’s begin.  Suppose I have a company that has a hierarchy of management as follows: Manager, Director, CEO.   Each level of management has a certain spending amount he/she can approve according to a specified budget.    If an amount is requested that exceeds the amount they can approve, the request is forwarded to the next level of management.

Step by step requirements (driven by tests):

Requirement #1:  We need a hierarchy of management with defined amounts that each can approve.  This hierarchy should consist of a Manager who can approve up to $1000,  a Director who can approve amounts up to $10,000 and a CEO that can approve amounts up to $100,000.

So right off the bat from the requirements,  we will need 3 classes: Manager, Director and CEO.  We start with a test that will try and create a Manager object and test that this Manager can approve an amount up to $1000

package com.chainofresponsibility
public class ManagerTest {
    private Manager manager;
    @Before
    public void setUp() {
        this.manager = new Manager();
    }
    @Test
    public void ApproveAmountsUpToMaxAllowable() {
        assertEquals(true, this.manager.approve(900));
    }
}

Here we are using Junit 4 style annotations, where @Before applied to a method (setUp()) will be executed prior to every test case.   The @Test annotation applied to a method means that method is of course a test.   The annotated approach of Junit 4 is much more readable, explicit and declarative.  Moreover, your test methods do not have to contain the keyword ‘test’ as in previous versions.

At this point, we don’t have a Manager class, let alone an approve() method, so running the above test will fail.   That is the idea 🙂 !!   We start with a failing test and we then write just the bare minimum amount of code to get the tests to pass.

So, we create a Manager class with an approve method such that the test will pass:

package com.chainofresponsibility;
public class Manager {
    public boolean approve(int amount) {
        if (amount <= 1000) {
            return true;
        }
    }
}

A couple of things to note here.   First, when creating a test class, by convention, it is a good idea to create the class in a source folder on the same level with the same package name.   i.e) here is the folder and package structure of the above 2 classes:

/project root
  /src
    /com.chainofresponsibility.Manager
  /test
    /com.chainofresponsibility.ManagerTest

Note the test method name as well: ApproveAmountsUpToMaxAllowable().   The test name should be very clear and concise and express its intent.   The test is essentially what acts as your documentation.   A simple glance at the method name clearly states the intention.   There are other styles like those  described in BDD where the same method name would look like this: WhenApprovalRequestLessThanMaxAllowableThenShouldApprove().  Don’t be afraid to be verbose!   In the great words of Martin Fowler,  “Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”Another thing to note is that the class was created in the simplest and fastest manner possible in order to get the tests to pass.    After running the above test, it now passes.   Now is the time to refactor because we have a test to back up the functionality!

Ok, so I don’t like the idea of hard coding the approval limit in the manager class so I’m going to refactor it by making it into a private instance variable (with a default value) and creating a getter and setter such that I can change this value on the fly:

public class Manager {
    private int approvalAmount = 1000;
    public int getApprovalAmount() {
        return this.approvalAmount;
    }
    public void setApprovalAmount(int approvalAmount) {
        this.approvalAmount = approvalAmount;
    }
    public boolean approve(int amount) {
        if (amount <= this.approvalAmount) {
            return true;
        }
    }
}

After my refactoring, I re-run my test and ensure that the test still passes and I haven’t broken anything.  Woohoo!  It still passes!  This is the beauty of this type of development paradigm:  you can re-factor with greater confidence.

The next element of the requirement is we need a second level of management: Director that has an approval limit of $10,000.  Again, we start with a test first before we code anything:

public class DirectorTest {
    private Director director;
    @Before
    public void setup() {
        this.director = new Director();
    }
    @Test
    public void ApproveAmountsUpToMaxAllowable() {
        assertEquals(true, this.director.approve(9000));
    }
}

Again, we don’t have a Director class with an approve method so when we run our test, it will fail.  (I left out package/import statements and getter/setter code for brevity).    Using the Manager class as a good and refactored example, we create the Director class in a similar fashion:

public class Director {
     private int approvalAmount = 10000;
    public boolean approve(int amount) {
        if (amount <= this.approvalAmount) {
            return true;
        }
    }
     //getters and setters here
}

We run the unit test for director and it passes.  The final element of the requirement is we need a final level of management: CEO that has an approval limit of $100,000.  Again, we start with a test first before we code anything:

public class CEOTest {
    private CEO ceo;
    @Before
    public void setup() {
        this.ceo = new CEO();
    }
    @Test
    public void ApproveAmountsUpToMaxAllowable() {
        assertEquals(true, this.ceo.approve(100000));
    }
}

Again, we don’t have a CEO class with an approve method so when we run our test, it will fail.   Let’s create the CEO class now:

public class CEO {
    public int approvalAmount = 100000;
    public boolean approve(int amount) {
        if (amount <= this.approvalAmount) {
            return true;
        }
    }
}

We have now fulfilled requirement #1.

Requirement #2:  If the first level of management cannot approve a given amount, the approval must go to the next manager in the hierarchy.   This should continue until the last level in the hierarchy at which point if no approval can be done, the approval is denied.

So given this requirement, let’s modify the Manager,Director and CEO classes accordingly.    We start with the Manager class.  If the requested amount exceeds the approval limit, we forward the request to the next in the hierarchy which is the Director.  Similarly, we apply the same logic for the Director but end at the CEO.   The Manager class will be modified by adding an ‘else’ clause, instantiating the Director who is next in line and forwarding the approval: (we omit getters/setters for brevity)

public class Manager {
    private int approvalLimit = 1000;
    public boolean approve(int amount) {
        if (amount <= this.approvalLimit) {
            return true;
        } else {
            Director director = new Director();
            return director.approve(amount);
        }
    }
    //getters/setters ommitted for brevity
}

And for the Director:

public class Director {
    private int approvalLimit = 10000;
    public boolean approve(int amount) {
        if (amount <= this.approvalLimit ) {
            return true;
        } else {
            CEO ceo = new CEO();
            return ceo.approve(amount);
        }
    }
    // omit getters and setters for brevity
}

And CEO:

public class CEO {
    private int approvalLimit = 100000;
    public boolean approve(int amount) {
        if (amount <= this.approvalLimit ) {
            return true;
        }
        return false;
    }
    // omit getters and setters for brevity
}

First thing we do is run all of our unit tests to ensure they are all functioning.  Note that the unit tests are minimal and need to also include cases where approval amounts do not get approved.   This is left for the reader as an exercise 🙂

What is wrong with the above approach?

For one, the hierarchy definition is very brittle and not resilient to change.   The Manager/Director/CEO objects are tightly coupled and each is aware of the other.  What if tomorrow the Director gets fired?   We would have to change the Manager class to now instantiate the CEO directly and forward the request to that object.   What if a new level of management is introduced?   Same idea, we’d have to make code changes to accommodate accordingly

Second, object graph creation is being done inside the class.   Miško Hevery – an Agile coach at Google –  states in one of his blogs:

Mixing object graph construction with application logic: In a test the thing you want to do is to instantiate a portion (ideally just the class under test) of your application and apply some stimulus to the class and assert that the expected behavior was observed.  In order to instantiate the a class in isolation we have to make sure that the class itself does not instantiate other objects (and those objects do not instantiate more objects and so on). Most developers freely mix the “new” operator with the application logic. In order to have a testable code-base your application should have two kinds of classes. The factories, these are full of the “new” operators and are responsible for building the object graph of your application, but don’t do anything. And the application logic classes which are devoid of the “new” operator and are responsible for doing work. In test we want to test the application logic. And because the application logic is devoid of the “new” operator, we can easily construct an object graph useful for testing where we can strategically replace the real classes for test doubles.

As such, we need to inject the dependency or ask for it so to speak.

So let’s start to re-factor.   Basically, as discussed, I would like to de-couple the Manager/Director and CEO such that none of those entities needs to really know who’s next in line.    I would also like to inject the current objects successor instead of instantiating it.   In order to do this we’ll need some form of polymorphism that will define a base type.   If given the choice, I personally prefer interfaces to abstract classes as that does not tie the class down to a single parent.   But some may argue otherwise 🙂     So I’ll introduce an Employee interface which will have the one approve() abstract method and which all classes will implement:

public interface Employee {
    public abstract boolean approve(int amount);
}

I’ll start by modifying the Manager class to implement this interface but I will also now NOT instantiate the Director class as the successor from within Manager but instead inject an Employee instance (supervisor) which in the Manager’s case will be a Director.   I can do this in 2 ways.  I could create a setter method to inject the supervisor in the Manager class or I could create a parametrized constructor for injection.   What is the advantage of one over the other?   No one explains it better than Miško Hevery in his article Constructor Injection vs. Setter Injection.  In a nutshell, constructor-injection enforces the order of initialization and prevents circular dependencies. With setter-injection it is not clear in which order things need to be instantiated and when the wiring is done.  Moreover, the API is not lying to you.   You know exactly what it needs as a dependency right from initialization.

So let’s begin.   I will also include the approval amount as one of the parameters and remove any default values defined for each class:

Re-factored Manager Class:

public class Manager implements Employee {
    private int approvalAmount;
    private Employee supervisor;
    public Manager(Employee supervisor, int approvalAmount) {
        this.supervisor = supervisor;
        this.approvalAmount = approvalAmount;
    }
    @Override
    public boolean approve(int amount) {
        if (amount <= this.approvalAmount) {
            return true;
        } else {
            return this.supervisor.approve(amount);
        }
    }
}

As you can see now, the Manager has no idea who the supervisor is, only that he/she is of type Employee and has an approve() method.    So if approval cannot be done at the Manager level, the next level in the hierarchy or ‘chain’ is called.   If we run our ManagerTest, it will still pass (unless you’ve made some drastic changes 🙂     So we re-factor the ManagerTest to now create instances of the required supervisors (Employees) to be injected into the chain of classes.  This is what would typically be done in a Spring managed container.

public class ManagerTest {
    private Manager manager;
    private CEO ceo;
    private Director director;
    @Before
    public void setUp() {
        /**
         * Create instances of the required supervisors (Employees) to be
         * injected into the chain of classes.  This is what would typically
         * be done by Spring
         */
        this.ceo = new CEO(100000);
        this.director = new Director(this.ceo, 10000);
        this.manager = new Manager(this.director, 1000);
    }
    @Test
    public void ApproveAmountsUpToMaxAllowable() {
        assertEquals(true, this.manager.approve(900));
    }
    @Test
    public void ApproveAmountBeyondMaxAllowable() {
        assertEquals(true, this.manager.approve(9000));
    }
  
    //Add more meaningful tests here
}

The appropriate re-factorings to Director & CEO have been left out for brevity but follow similar ideas.   What we have effectively done is implemented the Chain of Responsibility design pattern where essentially we have a ‘chain’ of objects in which:

“Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain”

Each object is not directly aware of the other which makes maintenance extremely easy and problems we discussed earlier very easy to fix.   You can also see this pattern in any J2EE compliant application.    The core J2EE pattern of the Intercepting Filter creates pluggable filters to process common services in a standard manner without requiring changes to core request processing code. The filters intercept incoming requests and outgoing responses, allowing pre-processing and post-processing.   One can add and remove these filters unobtrusively, without requiring changes to the existing code.

I hope you’ve found the above somewhat useful and you endeavor to try and write tests FIRST to guide your design.   By writing the tests first, you will be inclined to write cleaner, smaller, more manageable code as a natural consequence.

I’m trying my best to do so in my day to day work and also to get a better understanding of patterns that can more often than not make brittle solutions into elegant ones.

Assigned Tags

      2 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Former Member
      Former Member

      Excellent! Thanks for sharing.

      Author's profile photo Former Member
      Former Member

      Great examples and clear explanation!