September 16, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Getting Test Doubles in Place

  • January 3, 2008
  • By Jeff Langr
  • Send Email »
  • More Articles »

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;
      }
   };
}




Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel