Working With Design Patterns: Chain of Responsibility
Listing 4: JUnit tests.
import static org.junit.Assert.*; import org.junit.*; public class ChainTest { private Manager manager; private VicePresident vp; private CEO ceo; private ExpenseReport report; private static final int SMALL_AMOUNT = 100; private static final int VP_AMOUNT = 7000; private static final int CEO_AMOUNT = 207000; @Before public void amountPreconditions() { assertTrue(SMALL_AMOUNT <= manager.getDollarLimit()); assertTrue(manager.getDollarLimit() < VP_AMOUNT && VP_AMOUNT <= vp.getDollarLimit()); assertTrue(vp.getDollarLimit() < CEO_AMOUNT); } @Before public void createReport() { report = new ExpenseReport(); } @Before public void createDefaultChain() { manager = new Manager("Joe", false); vp = new VicePresident("Jane"); ceo = new CEO("Zeus"); manager.setNextApprover(vp); vp.setNextApprover(ceo); } @Test public void approvedByManager() { report.setTotalDollarAmount(SMALL_AMOUNT); manager.handle(report); assertApprovedBy(manager); } @Test public void approvedByVPWhenOverManagerLimit() { report.setTotalDollarAmount(VP_AMOUNT); manager.handle(report); assertApprovedBy(vp); } @Test public void approvedByCEOWhenOverVPLimit() { report.setTotalDollarAmount(CEO_AMOUNT); manager.handle(report); assertApprovedBy(ceo); } @Test public void approvedIntlByIntlManager() { report.setTotalDollarAmount(SMALL_AMOUNT); report.setIsInternationalTravel(true); manager.setCanApproveInternational(true); manager.handle(report); assertApprovedBy(manager); } @Test public void approvedIntlByVPWhenManagerNotIntl() { report.setTotalDollarAmount(SMALL_AMOUNT); report.setIsInternationalTravel(true); assertFalse(manager.canApproveInternational()); manager.handle(report); assertApprovedBy(vp); } @Test public void approvedByVPWhenManagerOutOfOffice() { report.setTotalDollarAmount(SMALL_AMOUNT); manager.setOutOfOffice(true); manager.handle(report); assertApprovedBy(vp); } @Test public void rejectedWhenCEOOutOfOffice() { report.setTotalDollarAmount(CEO_AMOUNT); ceo.setOutOfOffice(true); manager.handle(report); assertSame(State.rejected, report.state()); } @Test public void approvedByPeerWhenManagerOutOfOffice() { report.setTotalDollarAmount(SMALL_AMOUNT); Manager peer = new Manager("Jim", false); peer.setNextApprover(vp); manager.setOutOfOffice(true); manager.setNextApprover(peer); manager.handle(report); assertApprovedBy(peer); } private void assertApprovedBy(Approver approver) { assertSame(State.approved, report.state()); assertSame(approver, report.getHandler()); } }
The client working with the chain of responsibility doesn't know and doesn't really care which approver will finally handle the expense report. In this implementation, the employee submitting the report knows the initial approver—his or her manager. However, there's no reason that the client even needs to know this much information. A factory could produce an appropriate Approver implementation for the client.
Figure 1: Chain of Responsibility.
About the Author
Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff has contributed a couple chapters to Uncle Bob Martin's upcoming book, Clean Code. Jeff has written over 75 articles on software development, with over thirty appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.
Page 3 of 3
This article was originally published on May 7, 2008