Taking the BDD GIVEN, WHEN & THEN Scenario approach to unit testing has a number of benefits. I find it makes tests easier to maintain as you stick to one assertion per test. The test scenarios are well partitioned making them cleaner than straight forward Arrange Act Assert style. It also produces more readable output of test results.
Automocking is a time-saving feature of StructureMap that allows you to easily instantiate the Class Under Test (the class that will be used in the GIVEN statement of the Scenario) by automatically resolving the constructor dependencies of the class with Mocked out instances. Test classes therefore become decoupled from the constructor(s) of the Class Under Test. Meaning that you'll get a test that fails if you change a constructor instead of a test that won't build. This is a debatable point but can be considered an advantage in TDD - you get feedback from your tests as opposed to the compiler, as it should be.
By installing the StructureMap.AutoMocking and Moq packages you get out-of-the-box support for AutoMocking. Given that I regularly use StructureMap and Moq as my IOC and mocking framework of choice respectively, it makes sense to take advantage.
An Example
In the example class to test there are two dependencies that will need to be automatically mocked, and a method that should be tested.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class TaskRunner | |
{ | |
private IContextProvider _contextProvider; | |
private ITask _task; | |
public TaskRunner(IContextProvider contextProvider, ITask task) | |
{ | |
_contextProvider = contextProvider; | |
_task = task; | |
} | |
public void RunTaskWithContext() | |
{ | |
var context = _contextProvider.GetContext(); | |
_task.Run(context); | |
} | |
} | |
public interface IContextProvider | |
{ | |
string GetContext(); | |
} | |
public interface ITask | |
{ | |
void Run(string context); | |
} |
The base class provides us with the GIVEN & WHEN syntax. It uses the MoqAutoMocker provided by the StrutureMap.Automocking library to mock any dependencies on our class's constructor. It provides a handle to access any Mocked dependency that was injected when the class was instantiated. Also provided is a shortcut to Moq's Verify method as well as some syntactic sugar that lets you use [Then] instead of [Test] to round off the BDD style.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class GivenA<T> where T : class | |
{ | |
private MoqAutoMocker<T> _autoMocker = new MoqAutoMocker<T>(); | |
protected T Target { get; private set; } | |
[SetUp] | |
public void Setup() | |
{ | |
Given(); | |
When(); | |
} | |
protected virtual void Given() | |
{ | |
Target = _autoMocker.ClassUnderTest; | |
} | |
protected virtual void When() | |
{} | |
protected Mock<TT> GetMock<TT>() where TT : class | |
{ | |
return Mock.Get(_autoMocker.Get<TT>()); | |
} | |
protected void Verify<TT>(Expression<Action<TT>> verify) where TT : class | |
{ | |
Mock.Get(_autoMocker.Get<TT>()).Verify(verify); | |
} | |
} | |
public class ThenAttribute : TestAttribute | |
{} |
The example unit test class implements the abstract class and is named according to the method being tested. There should be one class per method being tested - i.e. one WHEN statement per scenario. If there is another public method it should be tested in a new class that also implements the base class. This practice is what makes the test output easy to digest.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class WhenIRunTaskWithContext : GivenA<TaskRunner> | |
{ | |
private string _context = "context code"; | |
public override Given() | |
{ | |
base.Given(); | |
GetMock<IContextProvider>().Setup(m => m.GetContext()).Returns(_context); | |
} | |
public override void When() | |
{ | |
Target.RunTaskWithContext(); | |
} | |
[Then] | |
public void TheContextIsFetched() | |
{ | |
Verify<IContextProvider>(m => m.GetContext()); | |
} | |
[Then] | |
public void TheTaskIsRunWithContext() | |
{ | |
Verify<ITask>(m => m.Run(_context)); | |
} | |
} |