Refining Context/Specification BDD using Rhino Mocks 3.5
October 25, 2008Earlier this year, I wrote this blog post about exploring Behavior-Driven Development as a better way of doing Test-Driven Development. In this post, I spoke about how to organize unit tests by their context and about how to apply a fluent language approach for naming these contexts/specifications. Here is how the example code of the context/specification from that post looks like:
[TestFixture]
[Category("RecordServiceTestFixture")]
public class When_retrieving_all_records_for_a_specific_genre
: Specification<RecordService>
{
private const Int64 GenreId = 12;
private Genre _genre;
private IEnumerable<Record> _records;
protected override void Before_each_specification()
{
_genre = new Genre();
_records = new List<Record>();
SetupResult.For(MockGenreRepository.FindBy(0))
.IgnoreArguments()
.Return(_genre);
SetupResult.For(
MockRecordRepository.GetAllRecordsFor(null))
.IgnoreArguments()
.Return(_records);
}
[Test]
public void Then_find_the_genre_for_the_specified_id()
{
BackToRecord(MockGenreRepository);
using(Record)
{
Expect.Call(MockGenreRepository.FindBy(GenreId))
.Return(_genre);
}
using(Playback)
{
CreateSubject().GetAllRecordsForGenre(GenreId);
}
}
[Test]
public void Then_find_all_records_for_a_specific_genre()
{
BackToRecord(MockRecordRepository);
using(Record)
{
Expect.Call(
MockRecordRepository.GetAllRecordsFor(_genre))
.Return(_records);
}
using(Playback)
{
CreateSubject().GetAllRecordsForGenre(GenreId);
}
}
[Test]
public void Should_return_a_list_of_records()
{
using(PlaybackOnly)
{
IEnumerable<Record> records =
CreateSubject().GetAllRecordsForGenre(GenreId);
Assert.That(records, Is.SameAs(_records));
}
}
private IGenreRepository MockGenreRepository
{
get { return Mock<IGenreRepository>(); }
}
private IRecordRepository MockRecordRepository
{
get { return Mock<IRecordRepository>(); }
}
}
I’ve been practicing this Context/Specification style of BDD since I wrote that post and I learned a couple of things since then. The code in this simple example still uses the Record/Playback plumbing that was then required by Rhino Mocks. The latest version of Rhino Mocks now supports the new AAA (Arrange, Act, Assert) syntax. I tried to make the code of this example a bit more easier to read by removing the noise of the Record/Playback syntax and some refactoring. Here is what I came up with:
[TestFixture]
[Category("RecordServiceTestFixture")]
public class When_retrieving_all_records_for_a_specific_genre
: AutoInstanceSpecification<RecordService>
{
protected override void Establish_context()
{
genre = new Genre();
records = new List<Record>();
MockGenreRepository.Expect(genreRepository
=> genreRepository.FindBy(GenreId))
.Return(genre);
MockRecordRepository.Expect(recordRepository
=> recordRepository.GetAllRecordsFor(genre))
.Return(records);
}
protected override void Because()
{
result = SUT.GetAllRecordsForGenre(GenreId);
}
[Test]
public void Then_find_the_genre_for_the_specified_id()
{
MockGenreRepository.AssertWasCalled(repository
=> repository.FindBy(GenreId));
}
[Test]
public void Then_find_all_records_for_a_specific_genre()
{
MockRecordRepository.AssertWasCalled(repository
=> repository.GetAllRecordsFor(genre));
}
[Test]
public void Should_return_a_list_of_records()
{
Assert.That(result, Is.SameAs(records));
}
private IGenreRepository MockGenreRepository
{
get { return Mock<IGenreRepository>(); }
}
private IRecordRepository MockRecordRepository
{
get { return Mock<IRecordRepository>(); }
}
private const Int64 GenreId = 12;
private Genre genre;
private IEnumerable<Record> records;
private IEnumerable<Record> result;
}
As you can see, the test cases are now reduced to a single line of code. The only thing that remains are the asserts itself. The actual call to the subject-under-test is nicely tucked away in the Because method, which is executed before each test case. This is something I’ve picked up by reading this article from Scott Bellware (which is highly recommended!!) and by looking at SpecUnit. Setting up the context is still done by the Establish_context method and the AutoMockingContainer is still used by the base class.
I’ve also split up the base test fixture of my previous post into three different classes:
public abstract class Specification
{
[SetUp]
public virtual void BaseSetUp()
{
Establish_context();
Initialize_subject_under_test();
Because();
}
[TearDown]
public virtual void BaseTearDown()
{
Dispose_context();
}
protected virtual void Establish_context() {}
protected virtual void Initialize_subject_under_test() { }
protected virtual void Because() {}
protected virtual void Dispose_context() {}
}
public abstract class InstanceSpecification<TSubjectUnderTest>
: Specification
{
protected override void Initialize_subject_under_test()
{
SUT = Create_subject_under_test();
}
protected abstract TSubjectUnderTest
Create_subject_under_test();
protected TSubjectUnderTest SUT
{ get; private set; }
}
public abstract class AutoInstanceSpecification<TSubject>
: InstanceSpecification<TSubject>
{
private MockRepository _mockRepository;
private AutoMockingContainer _autoMockingContainer;
protected AutoMockingContainer AutoMockingContainer
{
get { return _autoMockingContainer; }
}
protected MockRepository MockRepository
{
get { return _mockRepository; }
}
protected override TSubject Create_subject_under_test()
{
return _autoMockingContainer.Create<TSubject>();
}
protected TMock Mock<TMock>() where TMock : class
{
return GetDependency<TMock>();
}
protected TStub Stub<TStub>() where TStub : class
{
_autoMockingContainer.Mark<TStub>().Stubbed();
return GetDependency<TStub>();
}
private TDependency GetDependency<TDependency>()
where TDependency : class
{
var dependency = _autoMockingContainer
.Get<TDependency>();
if(false == MockRepository.IsInReplayMode(dependency))
{
MockRepository.Replay(dependency);
}
return dependency;
}
public override void BaseSetUp()
{
_mockRepository = new MockRepository();
_autoMockingContainer =
new AutoMockingContainer(_mockRepository);
_autoMockingContainer.Initialize();
base.BaseSetUp();
}
public override void BaseTearDown()
{
base.BaseTearDown();
_autoMockingContainer = null;
_mockRepository = null;
}
}
This has the advantage that now all test fixtures who don’t need to any mock objects can be derived from the InstanceSpecification base class:
[TestFixture]
[Category("RecordTestFixture")]
public class When_adding_a_track_to_a_record
: InstanceSpecification<Record>
{
protected override void Because()
{
SUT.AddTrack(TrackName);
}
[Test]
public void Then_the_record_should_have_the_specified_track()
{
Assert.That(SUT.HasTrack(TrackName));
}
protected override Record Create_subject_under_test()
{
return new Record(RecordName);
}
private const string RecordName = "Homework";
private const string TrackName = "Rollin' & Scratchin'";
}
So far, I like this way of using BDD style specifications but I would love to hear any thoughts, remarks, flames, etc. … . I guess that this topic is still somewhat of a moving target, so I’m eager learn and further refine my approach.
Till next time
If you and your team want to learn more about how to write maintainable unit tests and get the most out of TDD practices, make sure to have look at our trainings and workshops or check out the books section. Feel free to reach out at info. @ principal-it .be
Jan Van Ryswyck
Thank you for visiting my blog. I’m a professional software developer since Y2K. A blogger since Y2K+5. Provider of training and coaching in XP practices. Curator of the Awesome Talks list. Past organizer of the European Virtual ALT.NET meetings. Thinking and learning about all kinds of technologies since forever.
Comments
Writing Maintainable
Unit Tests
Watch The Videos
Latest articles
-
Contract Tests - Parameterised Test Cases
June 28, 2023
-
Contract Tests - Abstract Test Cases
April 12, 2023
-
Contract Tests
February 1, 2023
-
The Testing Quadrant
June 15, 2022
-
Tales Of TDD: The Big Refactoring
February 2, 2022
Tags
- .NET
- ALT.NET
- ASP.NET
- Agile
- Announcement
- Architecture
- Behavior-Driven Development
- C++
- CQRS
- Clojure
- CoffeeScript
- Community
- Concurrent Programming
- Conferences
- Continuous Integration
- Core Skills
- CouchDB
- Database
- Design Patterns
- Domain-Driven Design
- Event Sourcing
- F#
- Fluent Interfaces
- Functional Programming
- Hacking
- Humor
- Java
- JavaScript
- Linux
- Microsoft
- NHibernate
- NoSQL
- Node.js
- Object-Relational Mapping
- Open Source
- Reading
- Ruby
- Software Design
- SourceControl
- Test-Driven Development
- Testing
- Tools
- Visual Studio
- Web
- Windows
Disclaimer
The opinions expressed on this blog are my own personal opinions. These do NOT represent anyone else’s view on the world in any way whatsoever.
About
Thank you for visiting my website. I’m a professional software developer since Y2K. A blogger since Y2K+5. Author of Writing Maintainable Unit Tests. Provider of training and coaching in XP practices. Curator of the Awesome Talks list. Thinking and learning about all kinds of technologies since forever.
Latest articles
Contract Tests - Parameterised Test Cases
Contract Tests - Abstract Test Cases
Contract Tests
The Testing Quadrant
Contact information
(+32) 496 38 00 82
info @ principal-it .be