A Non-Framework Based “Given When Then” Unit Testing Style
I was a tad sceptical about the whole BDD thing I first heard about it (I think it was badly sold to me). It took me a while and the help of Anthony Marcarno (amongst others) to see the value of the Scenario based “Given When Then” way of developing acceptance criteria – and hence tests. Initially (where I was working at the time) we derived real value from simply using was of framing stories to escape the trap of thinking purely in terms of our technical tasks when planning stories. The example/scenario based approach was easy for our (non-technical) product owner and main stakeholder to understand and participate in. This alone is justification enough for the so-called Behaviour Driven Development approach – although I always preferred the term “Acceptance Test Driven Development“.
So when I went to the SpecFlow sessions at the Progressive .Net Tutorials in 2010 I was quite attracted to that framework as a means of driving test code generation from Cucumber specifications (feature files) that could (in theory) be written by our stakeholder or a non-technical analyst. Unfortunately this all came towards the end of a long project, so we never really managed to give the SpecFlow approach a thorough rinse.
I’m now fortunate to be working in a development environment at Esendex where TDD is a fundamental aspect of our approach. So, it’s no surprise there are to very smart and TDD experienced people I get to work with who are constantly looking at improving practises in this area.
So, to cut a long story short, a style evolved earlier this year from the team that does not rely anything but Moq and NUnit as external dependencies in our testing code. We explored StoryQ, FitNesse and SpecFlow (amongst others I think). However, since Esendex has a much more developer centred environment (where project execution is concerned). The (direct) stakeholders are often technical anyway, these frameworks were often found to get in the way of good testing code quality. What resulted was a team testing style that allows for a behaviour driven approach – for unit level, integration and end-t0-end acceptance tests. So behaviour required by customers can drive the development of code more clearly.
So here’s an example story & scenario:
Story:
As a website user
I want to be redirected back to the home page when I successfully log in
So I can carry on using the website with member enabled features
So a simple “happy path” scenario would be:
Given a login page
When valid username/password combination is entered
Then the credentials are used to authenticate
And the user is redirected back to the home page
So, if we were to use C# to build an ASP.Net MVC web application and write test code to cover this scenario in our “old” style (using NUnit and Moq) we could come up with the following simple test fixture for this scenario:
using System.Web.Mvc;
using ExampleTDDWebApp.Web.Controllers;
using ExampleTDDWebApp.Web.Models;
using Moq;
using NUnit.Framework;
namespace ExampleTDDWebApp.Tests
{
[TestFixture]
public class AuthenticationControllerTests
{
[Test]
public void Login_WithValidUserCredentialsRedirectsUserToHomePage()
{
const string username = "bill@ben.com";
const string password = "flobberdob";
var mockAuthenticationService = new Mock();
mockAuthenticationService
.Setup(service => service.AuthenticateUserCredentials(username, password))
.Returns(true);
var authenticationController = new AuthenticationController(mockAuthenticationService.Object);
var actionResult = (RedirectToRouteResult) authenticationController.Login(username, password);
Assert.That(actionResult.RouteValues["controller"], Is.EqualTo("Home"));
Assert.That(actionResult.RouteValues["action"], Is.EqualTo("Index"));
mockAuthenticationService.VerifyAll();
}
}
}
So that’s not too bad – we’ve driven the code design from tests (simplistic as it is), but it doesn’t really reflect the Scenario – at least in terms of the way it reads. If test code is supposed to act as documentation then it isn’t clear what the scenario was that this fixture came out of. Also we’ve got 2 clear assertions in there and one slightly less clear one (in the form of the mock verification). The other thing here is that the arrange/act part of the test is all in the one test method – again, potentially making it less readable.
How about this then:
using System.Web.Mvc;
using ExampleTDDWebApp.Web.Controllers;
using ExampleTDDWebApp.Web.Models;
using Moq;
using NUnit.Framework;
namespace ExampleTDDWebApp.Tests
{
[TestFixture]
public class AuthenticationControllerLoginSuccessfulTests
{
private const string VALID_USERNAME = "bill@ben.com";
private const string VALID_PASSWORD = "flobberdob";
private Mock _mockAuthenticationService;
private RedirectToRouteResult _actionResult;
[SetUp]
public void GivenALoginController_WhenUSerSuppliesValidCredentials()
{
_mockAuthenticationService = new Mock();
_mockAuthenticationService
.Setup(service => service.AuthenticateUserCredentials(It.IsAny(), It.IsAny()))
.Returns(true);
_actionResult = (RedirectToRouteResult)new AuthenticationController(_mockAuthenticationService.Object).Login(VALID_USERNAME, VALID_PASSWORD);
}
[Test]
public void ThenTheAuthenticationServiceIsCalledWithTheCredentials()
{
_mockAuthenticationService.Verify(service => service.AuthenticateUserCredentials(VALID_USERNAME, VALID_PASSWORD));
}
[Test]
public void ThenRedirectionGoesToTheHomeController()
{
Assert.That(_actionResult.RouteValues["controller"], Is.EqualTo("Home"));
}
[Test]
public void ThenRedirectionGoesToIndexAction()
{
Assert.That(_actionResult.RouteValues["action"], Is.EqualTo("Index"));
}
}
}
That’s loads better:
- Tests clearly separated – this case one assertion/verification per test method.
- Setup and Test methods reflect the scenario – and hence required behaviour
- Setup (Given and When) is less dense.
- Verification on the mock Authentication service is more explicit