6.8 How Does the Test Get to the Mock?


6.8 How Does the Test Get to the Mock?

In all our previous examples, we had little trouble foisting the dummy or mock object on the test object. While the interface of the valueInEuro() method passed ExchangeRateProvider in our Euro calculator example, LogServer was passed to Logger within the constructor in our logger example. The choice between the two options depends on different points:

  • If we add the helper object as parameter to the corresponding method, as in EuroCalculator, we can reuse the OUT with different helper instances. In turn, we have to consider carefully where to take the correct helper instance from, without stealing it, for each method call.

  • If the helper object is passed in the constructor of the test object, as in our LogServer, then we will never have to think again which instance we will need when. This is meaningful mainly when the helper is needed in several methods of the object, and if it remains unchanged for the object's entire lifetime.

Either option lets us simply replace a helper or server object with dummy or mock objects. However, most existing programs were written without testing requirements in mind; that is, objects used internally are hardwired. The required helper objects are often created and maintained in instance variables during the initialization of an object. It is relatively easy to make such objects testable by offering additional methods to replace these helpers. Our EuroCalculator would then look like this:

 public class EuroCalculator {    private ExchangeRateProvider provider =       new ExchangeRateProvider();    public void setProvider(ExchangeRateProvider newProvider) {       provider = newProvider;    }    double valueInEuro(double amount, String currency) {...} } 

Accordingly, our test class has to explicitly replace the correct provider with a dummy provider in the test methods:

 public void testUSD2EUR() {    ExchangeRateProvider dummyProvider = new DummyProvider(1.1324);    EuroCalculator calculator = new EuroCalculator();    calculator.setProvider(dummyProvider);    double result = calculator.valueInEuro(1.5, "USD");    assertEquals(1.6986, result, ACCURACY); } 

We can see that the test is now longer and harder to read. In addition, there is a risk of forgetting to replace another component with its mock counterpart in more complex test scenarios. This can lead to subtle failures or errors in the test that will be hard to discover. However, we now have the benefit that the application code does not have to know anything about the replacement of a provider object.

Testing by use of dummies is even more difficult if a new server object is created in each place it is used, for example, to avoid synchronization problems. Our (simplified) valueInEuro() method would then look like the following:

 public double valueInEuro(double amount, String currency) {    ExchangeRateProvider provider = new ExchangeRateProvider();    double exchangeRate = provider.getRateFromTo(currency, "EUR");    return amount * exchangeRate; } 

In this case, our constructor invocation new ExchangeRateProvider() is nothing but an implicit constant, playing the same ugly role when the software is maintained and tested. If we change the constants, we need to search for all places they occur throughout the code—a first step towards the "maintenance trap."

Being determined to properly test such a method, we have to do major restructuring work: the ExchangeRateProvider has to be structured so that it can be replaced. If we do not want to pass the provider as an additional parameter, there is only one last trick left: instead of the instance itself, we pass a factory object. When testing, we can replace the factory object by a mock factory. To this end, we need an interface with two implementations:

 public interface ProviderFactory {    public ExchangeRateProvider createProvider(); } public class RealProviderFactory implements ProviderFactory {    public ExchangeRateProvider createProvider() {       return new ExchangeRateProvider();    } } public class MockProviderFactory implements ProviderFactory {    private double rate;    public MockProviderFactory(double rate) {       this.rate = rate;    }    public ExchangeRateProvider createProvider() {       return new DummyProvider(rate);    } } 

Now we can either pass the appropriate factory object in the EuroCalculator class with the constructor or replace it by using a setter method. The following would be a typical case:

 public void testUSD2EUR() {    ProviderFactory factory = new MockProviderFactory(1.1324);    EuroCalculator calculator = new EuroCalculator(factory);    double result = calculator.valueInEuro(1.5, "USD");    assertEquals(1.6986, result, ACCURACY); } 

However, the testability here is at the cost of simplicity and readability. The additional detour is harder to understand than a straight constructor invocation. To our advantage, though, we win independence of the EuroCalculator from a specific ExchangeRateProvider.




Unit Testing in Java. How Tests Drive the Code
Unit Testing in Java: How Tests Drive the Code (The Morgan Kaufmann Series in Software Engineering and Programming)
ISBN: 1558608680
EAN: 2147483647
Year: 2003
Pages: 144
Authors: Johannes Link

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net