Unused Constructor Dependencies


Some classes simply require multiple dependencies that don’t always get used.  When testing such a class, there are several options for supplying the constructor parameters that will not be used.  I prefer to write tests that are as intention-revealing as possible. 

To demonstrate, here is a simple example of a payment processing class.  Think of the credit card machines that are found at the local grocery store.  The basic flow is for the customer to approve the charge amount, then swipe their credit card or debit card and complete the transaction.  Below, the PaymentProcessor is responsible for displaying the current charge amount to a user, verifying that they accept the amount, and then sending transaction information to the bank to request funds.  Here is an example implementation:

 

   1: public class PaymentProcessor

   2: {

   3:   private readonly IBankInformationReader _bankInformationReader;

   4:   private readonly IBankService _bankService;

   5:   private readonly IUserVerification _ui;

   6:  

   7:   public PaymentProcessor(

   8:     IBankInformationReader bankInformationReader, 

   9:     IBankService bankService, 

  10:     IUserVerification ui)

  11:   {

  12:     _bankInformationReader = bankInformationReader;

  13:     _bankService = bankService;

  14:     _ui = ui;

  15:   }

  16:  

  17:   public bool ProcessPayment(double amount)

  18:   {

  19:     if (!_ui.VerifyAmount(amount))

  20:       return false;

  21:  

  22:     var bankInfo = _bankInformationReader.GetBankInformation();

  23:  

  24:     return _bankService.RequestFunds(amount, bankInfo);

  25:   }

  26: }

</div> </div>

 

The PaymentProcessor depends on IBankInformationReader, IBankService, and IUserVerification.  If the user declines the payment amount, then processing should stop immediately.  You can see this behavior above in lines 19-20.

Under this scenario, then PaymentProcessor should never interact with either the IBankInformationReader (used for reading the credit card swipe) or the IBankService (used for submitting the transaction).  My preference is to pass null for the dependencies that shouldn’t be used, but to do so in an intention-revealing way.  Here is an example:

 

   1: [Test]

   2: public void should_not_charge_if_the_user_declines_the_amount()

   3: {

   4:   double amount = 4.50;

   5:  

   6:   IBankInformationReader cardReaderShouldNotBeUsed = null;

   7:   IBankService bankServiceShouldNotBeUsed = null;

   8:   

   9:   var ui = Stub<IUserVerification>();

  10:   ui.Stub(x => x.VerifyAmount(amount)).Return(false);

  11:  

  12:   var processor = new PaymentProcessor(

  13:     cardReaderShouldNotBeUsed, bankServiceShouldNotBeUsed, ui);

  14:  

  15:   processor.ProcessPayment(amount).ShouldBeFalse();

  16: }

</div> </div>

 

Rather than simply passing the value null to the constructor in lines 12-13, I’ve used some local variables.  This allows the test to clearly show intent.  In this case, the test states that PaymentProcessor should never use the bank service or the card reader when the user declines the transaction.  And, by using a null, we know that the test will fail if the PaymentProcessor tries to use those dependencies. 

There is another option which is equally intention revealing but somewhat more noisy.  We could use a mocking framework to generate the dependencies and then verify at the end of the test that they have never been used:

 

   1: [Test]

   2: public void should_not_charge_if_the_user_declines_the_amount()

   3: {

   4:   double amount = 4.50;

   5:  

   6:   var cardReader = Stub<IBankInformationReader>();

   7:   var bankService = Stub<IBankService>();

   8:   

   9:   var ui = Stub<IUserVerification>();

  10:   ui.Stub(x => x.VerifyAmount(amount)).Return(false);

  11:  

  12:   var processor = new PaymentProcessor(

  13:     cardReader, bankService, ui);

  14:  

  15:   processor.ProcessPayment(amount).ShouldBeFalse();

  16:   cardReader.AssertWasNotCalled(x => x.GetBankInformation());

  17:   bankService.AssertWasNotCalled(x => x.RequestFunds(0, null), 

  18:     o => o.IgnoreArguments());

  19: }

</div> </div>

The problem that I have with this test is all the noise on lines 16-17.  I would rather reveal this intention through some well-named variables.

A Belated Introduction