I’m building a portfolio manager, something that will track my stock purchases. My current need is that it calculate the total value of its contained holdings. A holding is a stock symbol and the number of associated shares. The portfolio value is the sum of the current price of each symbol times the corresponding number of shares. An external service, perhaps supplied by NASDAQ, provides the ability to look up a stock’s value.
Were I to build Portfolio without considering the need to unit test it, the implementation might look like Listing 1.
Listing 1: Portfolio.
import java.util.*; public class Portfolio { private Map<String,Integer> holdings = new HashMap<String,Integer>(); public int value() { int total = 0; for (Map.Entry<String,Integer> entry: holdings.entrySet()) { String symbol = entry.getKey(); int shares = entry.getValue(); total += new NASDAQLookupService().currentValue(symbol) * shares; } return total; } public void purchase(String symbol, int shares) { holdings.put(symbol, shares); } }
I code using test-driven development, so my primary interest is in ensuring that there’s an easy way to drive and verify each line of code in the value method. The problem is that NASDAQ keeps returning a different value each time I send it a request for a symbol’s price. The solution is to provide what’s known as a “test double” for the NASDAQLookupService object. This test double will emulate the NASDAQLookupService behavior, but fix it for purposes of testing. (Other similar terms for test doubles are fakes, stubs, and mocks.)
When using test doubles, I have two primary concerns: first, what does the test double look like, and second, how do I incorporate it into the production code? With regard to the first concern, the test double is a simple implementation of the same interface used by the real object. For my Portfolio class, I create a fake StockLookupService that implements an interface named StockLookupService. NASDAQLookupService, too, implements this interface.
Now that I have a fake lookup service, how do I get it into the the class that I’m testing (the target class)? The usual solution is to pass the fake into the target via a constructor or setter. This technique is most common, and demonstrated by most introductory articles on mocking. I’ll present this rudimentary injection technique, and then focus on a couple additional ways to inject the test double.
Constructor/Setter Injection
In Listing 2, I present an altered Portfolio class. The constructor provides the ability for a client to pass in a reference to a StockLookupService. This reference can of course point to either a NASDAQLookupService or to a fake lookup service.
Listing 2: An injectable Portfolio.
import java.util.*; public class Portfolio { private StockLookupService service; private Map<String,Integer> holdings = new HashMap<String,Integer>(); public Portfolio(StockLookupService service) { this.service = service; } public int value() { int total = 0; for (Map.Entry<String,Integer> entry: holdings.entrySet()) { String symbol = entry.getKey(); int shares = entry.getValue(); total += service.currentValue(symbol) * shares; } return total; } public void purchase(String symbol, int shares) { holdings.put(symbol, shares); } }
A bit of relevant test code for Portfolio appears in Listing 3. The @Before initialize method instantiates an anonymous fake impementation of the StockLookupService. It then uses this to instantiate an instance of the target class Portfolio.
Listing 3: PortfolioTest.
import static org.junit.Assert.*; import org.junit.*; public class PortfolioTest { private static final int CURRENT_MSFT_VALUE = 100; private static final String MSFT = "MSFT"; private Portfolio portfolio; @Before public void initialize() { StockLookupService service = new StockLookupService() { public int currentValue(String symbol) { if (MSFT.equals(symbol)) return CURRENT_MSFT_VALUE; return 0; } }; portfolio = new Portfolio(service); } @Test public void multiplesValueByShares() { portfolio.purchase(MSFT, 10); assertEquals(CURRENT_MSFT_VALUE * 10, portfolio.value()); } }
The implication of constructor or setter injection is that clients now must do the work that Portfolio had accomplished. Instantiation of the NASDAQLookup service, previously an encapsulated detail of Portfolio, is now the responsibility of a client class. One possible solution is to overload the constructors: Portfolio implements a no-argument constructor that production clients use, while tests use the constructor that allows injection of a StockLookupService. See Listing 4.
Listing 4: Overloading constructors.
public Portfolio() { this(new NASDAQLookupService()); } public Portfolio(StockLookupService service) { this.service = service; }
The solution of overloaded constructors has an interesting, minor flaw (as do just about all mock implementations): It introduces code that by definition is not testable in isolation. Technically, that will always be the case, as long as I have need for a volatile service such as NASDAQ in my application. The key takeback from this understanding is that unit tests are never a complete solution: I always need to test my code from an integrated, end-to-end perspective.
Injecting Via Factory
An alternate way to inject the fake is to use a factory. This solution is all the more appropriate if I already have need for a factory—what if, for example, I need to alternate between using NASDAQ and the New York Stock Exchange? Test-first, this time, Listing 5 shows how to drive this. The factory is told to hold on to a fake via a set method. Upon test completion, the @After method tells the factory to reset itself, so that other tests do not inadvertently use the fake. The Portfolio class changes slightly to support this altered injection design (see Listing 6).
Listing 5: Test-driving factory injection.
import static org.junit.Assert.*; import org.junit.*; public class PortfolioTest { private static final int CURRENT_MSFT_VALUE = 100; private static final String MSFT = "MSFT"; private Portfolio portfolio; @Before public void initialize() { StockLookupService service = new StockLookupService() { public int currentValue(String symbol) { if (MSFT.equals(symbol)) return CURRENT_MSFT_VALUE; return 0; } }; StockLookupServiceFactory.set(service); portfolio = new Portfolio(); } @After public void blowOutMockFromFactory() { StockLookupServiceFactory.resetToDefault(); } @Test public void multiplesValueByShares() { portfolio.purchase(MSFT, 10); assertEquals(CURRENT_MSFT_VALUE * 10, portfolio.value()); } }
I rarely use the factory method of injection unless I already have a compelling need for a factory. Without such need, it ends up adding some small but questionable additional complexity to the application.
Listing 6: Injecting via a factory.
import java.util.*; public class Portfolio { private Map<String,Integer> holdings = new HashMap<String,Integer>(); public int value() { StockLookupService service = StockLookupServiceFactory.get(); int total = 0; for (Map.Entry<String,Integer> entry: holdings.entrySet()) { String symbol = entry.getKey(); int shares = entry.getValue(); total += service.currentValue(symbol) * shares; } return total; } public void purchase(String symbol, int shares) { holdings.put(symbol, shares); } }
Injecting Via Subclass Override
A third mechanism involves stubbing a method of the target class Portfolio itself. I refer to this as injection via subclass override. The test appears in Listing 7. The Portfolio instance actually tested is in fact an anonymous subclass of the real target, with a creation method overridden. This creation method is responsible for returning an instance of type StockLookupService. In production (see Listing 8), the real creation method returns a NASDAQLookupService, whereas the stub returns the fake.
Listing 7: Test-driving injection via subclass override.
@Before public void initialize() { final StockLookupService service = new StockLookupService() { public int currentValue(String symbol) { if (MSFT.equals(symbol)) return CURRENT_MSFT_VALUE; return 0; } }; portfolio = new Portfolio() { @Override protected StockLookupService createStockLookupService() { return service; } }; }
Listing 8: Injection via subclass override.
public class Portfolio { // ... public int value() { StockLookupService service = createStockLookupService(); int total = 0; for (Map.Entry<String,Integer> entry: holdings.entrySet()) { String symbol = entry.getKey(); int shares = entry.getValue(); total += service.currentValue(symbol) * shares; } return total; } protected StockLookupService createStockLookupService() { return new NASDAQLookupService(); } // ... }
The downside of injecting via subclass override is that, once again, I violate encapsulation. My test knows more about the implementation details of Portfolio than it would otherwise. The trouble is that I can no longer change such details without impacting or possibly breaking tests. That’s an acceptable cost because I value the ability to test far more than notions of design perfection. But, it’s a reminder that there might be trouble if I take this concept too far, and let my tests know more significant amounts of detail about the targets they verify.
Conclusion
There are other interesting ways of injecting test doubles than I presented here. I might, for example, consider using aspects. But these three techniques—constructor/setter injection, factory injection, and subclass override injection—are the ones that I consistently use. Using these different injection techniques gives me a bit more flexibility when it comes to incorporating fakes into a system. But, an important thing I must remember is that the very introduction of these fakes implies that I now have a “hole” in my system—something that I will be unable to unit test. I can’t neglect my integration tests!
About the Author
Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He’s authored two books and over 50 published articles on software development, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. 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.