Blog post .NET, Technical

Testing Abstract Types and Mother Pattern

Unit testing is something that should always be done. The tests should be written by the same developer of the code which is the subject of the testing. It should not be broken by future changes of object creations, it should test only functionality of the type and validate that correct steps are taken via verifying end results. Good code coverage (although not enough by itself as insurance of quality) is something that we should all strive for. Notice that there are a lot of “should”-s. Usually, unit tests are left for the end, or completion of the functionality, only for it to be later found out that something with a higher priority has come up. There are always some “if conditions” that are required to be satisfied before advancing to unit tests. Sometimes they are there because of close deadlines or low budgets. Other times, it may be only because of developer laziness or some “superior” methodology that he/she is following, like “I write good code that does not need to be tested. “or “My code will always work”.

If you end up writing unit test after all, you should make sure to write them in the best possible way, as they are scarce but precious to the application structure and health. One of the problems you end up with when testing some functionality is the data for the tests. How will you construct that data, which types do you need to use in that process, and are there any required properties that should always be set, or which constructor (if any) should you use? I am fond of using constructors and generally avoid using object initializers or creating constructor-less types. In the constructor we can define all dependencies that we need for our class. But should that be in the unit test class? Should this class know anything about that object creation process? We are unit testing functionalities and states before and after them, we do not care about initialization processes, only the end results. And what if, at some point in application development, we change how some of our objects are created by introducing some other parameters or just by removing it completely? Our tests will break, which means a lot of code rewriting, and this should not happen.

First thing we need to do in this separation of concerns process is to create an interface for our test subject (hopefully you have done this already. If not, you probably need to take a look at SOLID principles). I will be using the custom type IMessageQueue interface as an example:

public interface IMessageQueue
{
  bool IsEmpty { get; }
  bool IsActive { get; }
  bool IsReadonly { get; }
  int MessageCount { get; }

  string ReadMessage();
  
  void PutMessage(string message);

  void Deactivate(bool isReadOnly);

  void ClearQueue();
}

All properties and methods are self-explanatory. We can put and read messages, clear queue, mark it as read-only or deactivate it. In MessageQueue we can find the implementation of the interface above, with three constructors:

public class MessageQueue : IMessageQueue
{
  private Queue messageQueue;

  public bool IsEmpty => MessageCount == 0;

  public bool IsActive { get; private set; }

  public bool IsReadonly { get; private set; }

  public int MessageCount => messageQueue.Count;

  public MessageQueue()
  {
    messageQueue = new Queue();
    IsActive = true;
    IsReadonly = false;
  }

  public MessageQueue(ICollection messageCollection)
  {
    messageQueue = new Queue(messageCollection);
    IsActive = true;
    IsReadonly = false;
  }

  public MessageQueue(ICollection messageCollection, bool isReadOnly)
  {
    messageQueue = new Queue(messageCollection);
    IsActive = true;
    IsReadonly = isReadOnly;
  }

  public void ClearQueue()
  {
    if (IsActive && !IsReadonly)
    {
    messageQueue.Clear();
    }
  } 

  public void Deactivate(bool isReadOnly)
  {
    IsReadonly = isReadOnly;
    IsActive = false;
  }

  public void PutMessage(string message)
  {
    if (!string.IsNullOrEmpty(message) && IsActive && !IsReadonly)
    {
      messageQueue.Enqueue(message);
    } 
  } 

  public string ReadMessage() 
  { 
    if (IsActive && !IsEmpty) 
    {
      return messageQueue.Dequeue().ToString();  
    } 
    return string.Empty; 
  } 
}

Now that we have setup our logic, it is time for unit tests. I will be using Xunit in this example. The class that contains our tests is marked with abstract modifier. In tests, we only use the interface that we defined and its members. By doing this, we bind tests to functionalities from abstract type, we don’t worry about concrete implementations. When we need an instance, we simply create a new abstract method (or use an existing one) that will return abstract type (IMessageQueue in this case). These methods will be implemented by mother class later on. This would represent the Abstract part in the title of this post. Mother pattern comes into action when we actually need to create some concrete instances (MessageQueue in this case). By inheriting the class with tests, mother class is required to implement all abstract MessageQueue creation methods, and provide us with the requested instances. With inheriting we also gain ability to run these tests as unit tests won’t be recognized and discovered when they are placed in abstract class. General workflow goes as follows:

1. Create test method
2. Construct instance with appropriate abstract builder method
3. Create new abstract method if it does not yet exist in the unit test class
4. Provide implementations of those abstract methods in mother class
5. Take testing actions and make assertions

The third step may resemble TDD (Test Driven Development) in a way. We define what we expect, and implement it if it is not there already. Essentially, we declare what object and in which state we want to create it, and then proceed further on with testing. By doing this, we have separated unit tests and types tested, and gained test reusability. If we want to test another implementation, we just need to create an additional mother class. Now, at any given time in application development, newly introduced breaking changes in the object creation process will require only rewriting of the mother class as tests won’t be affected by this change. In the next image, you can see the contents of the test class. For those who have never worked with Xunit before, with Fact attributes we mark down unit tests. Naming of tests is done by following the convention SubjectOfTest_TestScenario_ExpectedResult. I find this way of naming very useful and practice it always. There are only a few tests in this example as my current goal is not code coverage. Notice the abstract builder methods at the bottom.

public abstract class MessageQueueTests
{
  [Fact]
  public void IMessageQueue_IsEmpty_Empty()
  {
    IMessageQueue messageQueue = CreateMessageQueue(); 
    Assert.True(messageQueue.IsEmpty);
  }

  [Fact]
  public void IMessageQueue_IsActive_Active()
  {
    IMessageQueue messageQueue = CreateMessageQueue(); 
    Assert.True(messageQueue.IsActive);
  }

  [Fact]
    public void IMessageQueue_IsNotEmpty_NotEmpty()
  {
    IMessageQueue messageQueue = CreateMessageQueueWithCollection(); 
    Assert.False(messageQueue.IsEmpty);
  }

  [Fact]
  public void Deactivate_DeactivateQueue_ReadOnly()
  {
    IMessageQueue messageQueue = CreateMessageQueueWithCollection(); 
    messageQueue.Deactivate(true);
    Assert.True(messageQueue.IsReadonly);
  }

  [Fact]
  public void IMessageQueue_IsReadOnly_ReadOnly()
  {
    IMessageQueue messageQueue = CreateMessageQueueWithReadOnlyCollection(); 
    Assert.True(messageQueue.IsReadonly);
  }

  protected abstract IMessageQueue CreateMessageQueue();

  protected abstract IMessageQueue CreateMessageQueueWithCollection(); 
  
  protected abstract IMessageQueue CreateMessageQueueWithReadOnlyCollection(); 
}

As mentioned above, mother class is the place where we implement our builder methods. I have put test data in this class as I was only checking if the queue has elements or not. If you need to reference test data in a unit test class (to check whether the correct message is inserted at the right place) you can always define it there and pass it on via builder methods (in my case they do not take any parameters). Contents of Mother class are shown below:

public class MessageQueueTester : MessageQueueTests
{
  private readonly string[] testData = { "First Message", "Second Message", "Third Message" };

  protected override IMessageQueue CreateMessageQueue() => new MessageQueue(); 

  protected override IMessageQueue CreateMessageQueueWithCollection() => new MessageQueue(testData);

  protected override IMessageQueue CreateMessageQueueWithReadOnlyCollection() => new MessageQueue(testData, true); 
}

I have uploaded this code to our github repository so you can take a look if something was not clear. Of course, if you have any questions or comments, you can always contact me via ognjen.babic@exlrt.com.

Contact us to discuss your project.
We're ready to work with you.
Let's talk