Making my Repository Factory Test-Friendly
Intro
In this blog post, only last week, I worked through the steps I took to implement Code First in my website using BDD (Behaviour Driven Development) and my existing class hierarchy based on the Repository Pattern. Importantly, remembering that an instance of my interface IDataContext was passed to the repository factory and this encapsulated interaction with the underlying data store.
Well, last week, I implemented an IDataContext that spoke to my database — and I confirmed this by using SpecFlow, the BDD tool, to write some specs and then run tests to confirm those specs. But I cannot really use this setup for testing all of my application, because I don’t want to interact with my database.
With a week passing by, and now being the weekend again (at the moment I am reading Pro ASP.NET MVC 2 on weeknights, plus gym and social stuff) I have the chance to continue work on the re-design of my website NTCoding.com. So…..what challenge awaits me this week?
This week I am going to create another IDataContext implementer, but this time it is for testing purposes, and as alluded to above, I don’t want to be talking to any databases (although I may eventually look into some “light databases”). Some collections in memory should be fine for my specs (according to YAGNI).
Knowing that all I need to do is adhere to the requirements of the IDataContext interface, I can be sure that as long as my new implementer of IDataContext meets this interface’s requirements then it will work happily with my repositories and I can start including those repositories in my higher-level tests.Writing the Specs
As per my last blog post, I’ll be writing specs, using SpecFlow for all my tests — even unit tests such as this. And again, this week should be quite easy in terms of writing specs because I can base them on the requirements of the interface.
Just to recap, here is the IDataContext interface, remembering that an instance of this is passed into the repository factory to provide access to all the domain entities previously added. You may also note that there is now a CancelChanges() method — a requirement driven by the new behaviours of the system.
public interface IDataContext
{
void Add(T newItem) where T : class;
IEnumerable Where(Expression<Func<T, bool>> expression) where T : class;
IEnumerable FindAll() where T : class;
void Delete(T itemToDelete) where T : class;
void SaveChanges();
void CancelChanges();
}
Below are the specs I wrote that will need to pass in order to confirm the new data context works as intended. As I mentioned above, it is quite easy when there is an interface your behaviour is based on.
Feature: CRUD Operations
In order to test parts of my application that interact with a database
As a Developer
I need a testing data context that doesn’t interact with a database
Scenario: Addition With Save Changes
Given I have added an item
And I save changes
When I try to retrieve the item
Then I should get the item back
Scenario: Addition Without Save Changes
Given I have added an item
And I don’t save changes
When I try to retrieve the item
Then I should not get the item back
Scenario: Deletion With Save Changes
Given I have added an item
And I save changes
And I delete the item
And I save changes
When I try to retrieve the item
Then I should not get the item back
Scenario: Deletion Without Save Changes
Given I have added an item
And I save changes
And I delete the item
And I don’t save changes
When I try to retrieve the item
Then I should get the item back
Scenario: Querying
Given I have added a book with the title Nick’s Book
And I save changes
When I query for books with the title Nick’s Book
Then I should retrieve a matching item
Scenario: FindAll
Given I have supplied ten books
And I save changes
When I request all books
Then I should receive ten booksImplementing the Specs
Working through the specs sequentially, and logically you could argue, my first task was to be able to add items to the context and to make sure they were being stored.
Because implementing each step is pretty obvious, I’ll just run through the first so you can understand my coding process (and hopefully comment on how you think I can improve or how you do things differently). After that I’ll discuss purely the new implementer of IDataContext
I was then kindly told by SpecFlow, to implement these steps:
[Given(@”I have added an item”)]
public void GivenIHaveAddedAnItem()
{
}
[Given(@”I save changes”)]
public void GivenISaveChanges()
{
}
[When(@”I try to retrieve the item”)]
public void WhenITryToRetrieveTheItem()
{
}
[Then(@”I should get the item back”)]
public void ThenIShouldGetTheItemBack()
{
}
Immediately with the first step, the implication is to interact with the data context. So in a BDD way, I specified how I wanted the code to work before implementing it (calling methods on objects that don’t exist).
[Given(@”I have added an item”)]
public void GivenIHaveAddedAnItem()
{
DataContext.Add(_book);
}
Implementing the subsequent three steps in the same way, I crafted:
[Given(@”I save changes”)]
public void GivenISaveChanges()
{
DataContext.SaveChanges();
}
[When(@”I try to retrieve the item”)]
public void WhenITryToRetrieveTheItem()
{
if(DataContext.FindAll
{
_returnedBook =
DataContext.FindAll
}
}
[Then(@”I should get the item back”)]
public void ThenIShouldGetTheItemBack()
{
Assert.IsNotNull(_returnedBook);
Assert.IsTrue(_returnedBook.Title == _book.Title);
}
Please note: book is an entity that already exists in this application
At the moment, I have no real entities, just calls to objects that don’t exist — based on the required behaviour (BDD).
So, I then used my buddy ReSharper to generate the DataContext class for me. After then telling the class to implement IDataContext, naming it TestDataContext, and implementing the Add feature, it looked like so:
public class TestDataContext : IDataContext
{
private List