Fun with Unit Tests – Testing abstract classes
Download the source for this article: Direct link
Summary
In this article I’ll be looking into a trickier part of unit testing– testing the functionality of classes who cannot be instantiated on their own, abstract classes.
I’ll show how to overcome this obstacle using “Mock Objects”, a technique which has a whole methodology behind it all on its own. We’ll see how mock objects provide us with abilities that let us query what’s happening inside our derived class.
The problem with abstract classes
Introduction
Unit testing is all good and well as a theoretical nirvana. “Yeah, we do unit tests” is a great answer to have when someone asks you, but achieving this is a process just like any other software development process. The overall idea looks great, but you come across problems that don’t fit your original plans, or things you can’t deal with the way you’re use to. Unit testing has several of these obstacles routed at the core of the mythology. One of those obstacles is the testing of objects that cannot be instantiated on their own (abstract classes are one of several manifestations of this problem). As I’ll demonstrate, this too is possible using Mock objects, objects that exist for the sole purpose of helping us in our task of testing.
The problem with abstract classes
So what’s the definition of the problem we are facing? Well, to run a test against a class, we need to have an instance of that class to work on. We need something to call methods on and get values back if needed. We need a real live object. In the case of abstract classes there’s no way to do this.
We could make the class non-abstract for the sole purpose of testing it, but that would violate one of the most important concepts of Unit Tests – they should not alter the behavior or the data of the tested application. Making changes to the design of an object model for no reason other than tests is not the course of action we want to take.
We need to ask ourselves what we want to test in an abstract class. Usually abstract classes contain the plumbing required by classes that will derive from them. What we want to know is if the derived classes, as clients to the services the abstract class provides, are getting all the services they need. This method means that we are essentially wanting to do a “black box” test on the abstract class and make sure that any derived class that’s going to use it will have what we want it to have.
So this shifts our focus a bit. How do we test that any “client” to the base class is receiving the services it needs? This could be quite a complex problem to test, because another important concept here is that we want to test only one thing at a time. If we test derived classes of the base class (which might be a design problem in itself) we are essentially testing the functionality of the base classes as well, not to mention we’ll have to build those classes for our tests to work at all!
One of the most elegant solutions to this problem is actually very simple in concept – we’ll build objects just for our tests!
Mock objects
Mock objects are a very handy technique to tests objects that are “mediators”. Instead of referring to a real domain object we call the Mock object which pretends to be the real object. The Mock object is used to validate any assertions and expectations we have of that object and we can fully interact with it while at the same time still have total control over the results of our object’s method calls. There’s a whole Mock Objects testing framework, which is an alternate view of unit testing.
You can learn more about Mock objects by starting from this link.
Mock objects come in very handy when we either want to test an object that “changes stuff” in our application and we want to stop it control it, or when we want to test an object that uses other objects in order to do its work. A data layer object that uses the database would be a good example of when we want to control such things (we never want our unit tests to corrupt live data, that’s rule #1, ask your DBA).
In this case we can use a Mock object to derive from our class and make sure that it, as a “client” to our class, receives all the necessary “services” it expects.
A simple project idea
In order to explain this in more “close to home” terms, I’ll make up a simple project task.
We’ve designed a simple object model in which we have an abstract class “Task” which will contain a “start” method. The class will be used to derive other tasks from it, but will provide each derived class with abstract methods that will need to be implemented: BeforeStart(), OnStart(), and AfterStart(). Each derived class will be able to use these methods to perform initialization before a task begins, the task itself when needed, and cleanup after the task has finished (think ServicedComponent type events).
Planning our first test
Ok. We have no code yet, but what do we test first? We want to make sure that for any derived class, calling the Start() method, actually triggers the BeforeStart(), OnStart(), and AfterStart() methods inside it. To do that, we’ll need an instance of a class derived from Task (which does not exist yet).
This is a perfect candidate for a Mock object. We can use a Mock object that will derive from out base class and will let us know if its inner methods were called.
We’ll want to used a very simple mechanism here.
Our first test
In our first test we’ll assume we have an object derived from Task and well call it’s Start() method. Then we’ll assert that all three inert methods were called.
[Test]
public void TestOnStartCalled()
{
MockTask task = new MockTask();
task.Start();
Assert.IsTrue(task.OnStartCalled);
Assert.IsTrue(task.BeforeStartCalled);
Assert.IsTrue(task.AfterStartCalled);
}
This code won’t compile because we don’t have any MockTask class defined.
Creating our Mock object
Our mock object will derive from Task. Notice we’re building it assuming Task already exists.
public class MockTask:Task
{
public bool OnStartCalled=false;
public bool BeforeStartCalled=false;
public bool AfterStartCalled=false;
}
The beauty of this is because this is our mock object we can make it do whatever we want. In this case we are simply adding flags to it. Those flags will have to change somehow later on, but that’s not our problem now. Now we’re concerned about making the code compile.
Our code still won’t compile because we haven’t created out Task class yet.
Creating our abstract class
public abstract class Task
{
public void Start()
{
}
}
Notice we’re making the class as simple as possible, just making our code compile.
Making the test fail
If we run the test now out code will compile, but the test will fail miserably. The derived class had no OnStart ,BeforeStart and AfterStart methods defined. Therefore it’s “called” flags will always remain false.
Making the test work
Let’s make the test work by adding the required functionality to our base class:
public abstract class Task
{
public void Start()
{
BeforeStart();
OnStart();
AfterStart();
}
//all base classes must implement this method
protected abstract void OnStart();
protected abstract void BeforeStart();
protected abstract void AfterStart();
}
public class MockTask:Task
{
public bool OnStartCalled=false;
public bool BeforeStartCalled=false;
public bool AfterStartCalled=false;
protected override void AfterStart()
{
AfterStartCalled=true;
}
protected override void BeforeStart()
{
BeforeStartCalled=true;
}
protected override void OnStart()
{
OnStartCalled=true;
}
}
Conclusion
“Be-a-utiful!” as Bruce almighty often remarks. We now have a repeatable test that makes sure our base class calls all the methods we require from it.
References
· MockObjects testing framework
· Download the source for this article: Direct link