Adapter pattern & Unit Testing

The Adapter pattern is a simple but powerful pattern when it comes to unit testing. I wanted to put a few notes together that explain how it is used and what are its benefits.

You can find a formal definition of the pattern somewhere else on the Web, i will not bore you with it, the basic idea is that you get an object that uses an interface that does not suit you and you wrap it in an object so that you can make it (adapt it) to use the interface you want. As an example let’s say a class called Player exists in some 3rd party library.

public class Player : IPlayer
{
   public void PlaySoccer()
   {;}
}

public class PlayerAdapter
{
   private IPlayer _player;
   public PlayerAdapter(IPlayer player)
   {
       _player = player;
   }
   public void PlayFootball()
   {
       _player.PlaySoccer();
   }
}

The example should be self explanatory. You have a player that PlaySoccer, howevever in your system you need to use an object with this functionality but you need to follow an interface that dictates that Players should use a method called PlayFootball. An adapter comes to the rescue.

Modern software development advocates the development of single responsibility objects, objects with a single very well defined purpose. Also objects need, in order to be easily testable and loosely coupled with other parts of the system, to have as few dependencies as possible and when dependencies do exist, they need to be injected in the constructor, manually or with the use of an IoC Container such as NInject or Unity.

There are however some dependencies that are tricky to handle and there are some that are so subtle we don’t even notice they exist. As an example let us check the following Account class.

public class Account: IAccount
{
   private readonly long _accountId;
   public long AccountId
   {
      get { return _accountId; }
   }
   public decimal CurrentBalance { get; private set; }
   public AccountStatus AccountStatus { get; private set; }

   public Account(long accountId, decimal openingBalance)
   {
      _accountId = accountId;
      CurrentBalance = openingBalance;
      AccountStatus = AccountStatus.Open;
   }

   public decimal Deposit(decimal deposit)
   {
      if (IsOpen)
         CurrentBalance += deposit;
      else
         throw new InvalidOperationException("Deposit not allowed. Account not open.");

      return CurrentBalance;
   }

   public decimal WithDraw(decimal withdrawal)
   {
      if (IsOpen && CurrentBalance >= withdrawal)
         CurrentBalance -= withdrawal;
      else
         throw new InvalidOperationException("Withdrawal not allowed. Account not open or not sufficient funds");

      return CurrentBalance;
   }

   public void TransferTo(Account destinationAccount, decimal amountToTransfer)
   {
      if (IsOpen && destinationAccount.IsOpen && amountToTransfer <= CurrentBalance
      && IsTransferAllowedAtThisTime())
      {
         destinationAccount.Deposit(amountToTransfer);
         this.WithDraw(amountToTransfer);
      }
      else
         throw new InvalidOperationException("Transfer not allowed.");
   }

   public bool IsTransferAllowedAtThisTime()
   {
      DateTime currentTime = DateTime.Now;
      return (currentTime.Hour >= 9 && currentTime.Hour <= 16);
   }

   public bool IsOpen
   {
      get { return AccountStatus == AccountStatus.Open; }
   }

   public void Close()
   {
      if (CurrentBalance == 0)
         AccountStatus = AccountStatus.Closed;
   }

}

The purpose of this class is to provide some basic bank account functionality. The interesting part for us is the method that transfers money from one account to the other. Let us assume that the business rules allow transfers to only happen between 9:00 and 16:00 local time. We create a simple IsTransferAllowedAtThisTime method. This uses DateTime.Now. We use this method in the TransferTo method. All looks fine so far. We go ahead and try to create a unit test for this.

        [Test]
        public void TestTransferWhenWithinAllowedHours()
        {
            //Arrange
            var sourceAccount = new Account(1, 200);
            var destinationAccount = new Account(2, 100);

            //Act
            sourceAccount.TransferTo(destinationAccount, 120);

            //Assert
            Assert.AreEqual(sourceAccount.CurrentBalance, 80);
            Assert.AreEqual(destinationAccount.CurrentBalance, 220);

            //Clean up
        }

We immediately see that we actually have no way to test what we want as we have a dependency to the static DateTime.Now property in our code. We cannot “inject” the values we want to this test. One might think that there is a simple solution to this problem and that is to actually pass the time as a parameter to the TransferTo method.

        public void TransferTo(Account destinationAccount, decimal amountToTransfer, DateTime currentTime)
        {
            if (IsOpen && destinationAccount.IsOpen && amountToTransfer <= CurrentBalance && IsTransferAllowedAtThisTime(currentTime))
            {
                destinationAccount.Deposit(amountToTransfer);
                this.WithDraw(amountToTransfer);
            }
            else
                throw new InvalidOperationException("Transfer not allowed.");
        }

While this is a possible solution for this simple example we need to keep 2 things in mind. First, we need to be passing additional parameters around from one method to another. For a large system with many dependencies like this, this may become cumbersome and error-prone. Second, we do not really solve the problem of the “static” dependency, we simply pass it one level up. Now the caller of the TransferTo method will have to create a valid, current DateTime value and pass it to our function. If we want to unit test the caller we may end up having the same issue we have with unit testing TransferTo.

The most elegant solution to our problem is the use of an Adapter which in this case is nothing more than a Wrapper around DateTime that can be injected into our Account object.

    public interface IDateTime
    {
        DateTime Now { get; }
    }

    public class DateTimeAdapter : IDateTime
    {
        public DateTime Now
        {
            get { return DateTime.Now; }
        }
    }

We can now see the improved code for the Account class and the unit test for TransferTo.

public class Account: IAccount
{
   private readonly long _accountId;
   private readonly IDateTime _dateTimeAdapter;
   public long AccountId
   {
      get { return _accountId; }
   }
   public decimal CurrentBalance { get; private set; }
   public AccountStatus AccountStatus { get; private set; }

   public Account(long accountId, decimal openingBalance, IDateTime dateTimeAdapter)
   {
      _accountId = accountId;
      CurrentBalance = openingBalance;
      AccountStatus = AccountStatus.Open;
      _dateTimeAdapter = dateTimeAdapter;
    }

    public decimal Deposit(decimal deposit)
    {
       if (IsOpen)
           CurrentBalance += deposit;
       else
           throw new InvalidOperationException("Deposit not allowed. Account not open.");

        return CurrentBalance;
    }

    public decimal WithDraw(decimal withdrawal)
    {
        if (IsOpen && CurrentBalance >= withdrawal)
            CurrentBalance -= withdrawal;
        else
            throw new InvalidOperationException("Withdrawal not allowed. Account not open or not sufficient funds");

        return CurrentBalance;
    }

    public void TransferTo(Account destinationAccount, decimal amountToTransfer)
    {
        if (IsOpen && destinationAccount.IsOpen && amountToTransfer <= CurrentBalance 
            && IsTransferAllowedAtThisTime())             
        {   
            destinationAccount.Deposit(amountToTransfer);                 
            this.WithDraw(amountToTransfer);             
        }             
        else                 
           throw new InvalidOperationException("Transfer not allowed.");         
    }         

     public bool IsTransferAllowedAtThisTime()         
     {             
         DateTime currentTime = _dateTimeAdapter.Now;             
         return (currentTime.Hour >= 9 && currentTime.Hour <= 16);
     }

     public bool IsOpen
     {
        get { return AccountStatus == AccountStatus.Open; }
     }

     public void Close()
     {
         if (CurrentBalance == 0)
             AccountStatus = AccountStatus.Closed;
     }

}

Notice how the DateTimeAdapter is used in IsTransferAllowedAtThisTime and how it is injected in the constructor. The unit test is now like this

        [Test]
        public void TestTransferWhenWithinAllowedHours()
        {
            //Arrange
            var sourceAccount = new Account(1, 200, new DateTimeTestHelper(new DateTime(2014, 5, 5, 13, 5, 6)));
            var destinationAccount = new Account(2, 100, new DateTimeAdapter());

            //Act
            sourceAccount.TransferTo(destinationAccount, 120);

            //Assert
            Assert.AreEqual(sourceAccount.CurrentBalance, 80);
            Assert.AreEqual(destinationAccount.CurrentBalance, 220);

            //Clean up
        }

        [Test]
        public void TestTransferWhenOutOfHours()
        {
            //Arrange
            var sourceAccount = new Account(1, 200, new DateTimeTestHelper(new DateTime(2014, 5, 5, 18, 5, 6)));
            var destinationAccount = new Account(2, 100, new DateTimeAdapter());

            //Act
            sourceAccount.TransferTo(destinationAccount, 120);

            //Assert
            Assert.AreEqual(sourceAccount.CurrentBalance, 200);
            Assert.AreEqual(destinationAccount.CurrentBalance, 100);

            //Clean up
        }

We use now a new class called DateTimeTestHelper that simply exists to allow us to test times in our unit tests.

    public class DateTimeTestHelper : IDateTime
    {
        public DateTimeTestHelper(DateTime dateTime)
        {
            Now = dateTime;
        }
        public DateTime Now { get; set; }
    }

A similar technique can be used with any sort of 3rd party or .NET dependencies that you may have. By creating an adapter or wrapper around these classes you allow yourself the flexibility of dependency injection, mocking and proper unit testing!

Leave a comment