This is a follow up to the 1st in this series: http://iwayneo.blogspot.co.uk/2012/10/building-www.html
And the second http://iwayneo.blogspot.co.uk/2012/10/building-www_16.html
And the third: http://iwayneo.blogspot.co.uk/2012/10/building-www_26.html
And the fourth: http://iwayneo.blogspot.co.uk/2012/10/building-wwwbrightonsausagecocom.html
Battery died on the train to Nottingham yesterday so I shall be starting back on it tonight! Looking back at my final sentence before the battery karked it I see: "Let's go and add the IRepository
After having a ganders I can see that I have added a MongoInstaller and MongoCollectionComponentLoader (wholesale robbed from https://gist.github.com/2427676 barr a couple of minor mods) which allows me to inject a lazy loaded MongoCollection
public class Repository<T> : IRepository<T> | |
{ | |
private readonly MongoCollection<T> docs; | |
public Repository(MongoCollection<T> docs) | |
{ | |
this.docs = docs; | |
} | |
public IList<T> GetAll() | |
{ | |
return docs.FindAll().Select(x=>x.As<T>()).ToList(); | |
} | |
public void Save(T doc) | |
{ | |
docs.Save(doc); | |
} | |
public void Dispose() | |
{ | |
throw new NotImplementedException(); | |
} | |
} |
And a test:
[Test] | |
public void When_fetching_all_documents_all_are_returned() | |
{ | |
const int expectedCategoryCount = 2; | |
var container = new WindsorContainer(); | |
container.Install(new MongoInstaller()); | |
container.Register(Component.For(typeof (IRepository<>)).ImplementedBy(typeof (Repository<>))); | |
var repository = container.Resolve<IRepository<Category>>(); | |
repository.Save(new Category | |
{ | |
Id = "test1" | |
}); | |
repository.Save(new Category | |
{ | |
Id = "test2" | |
}); | |
var result = repository.GetAll(); | |
result.Count().Should().Be(expectedCategoryCount); | |
} |
So, now we have the repository working with MongoDB (!!) and it's being injected into the controller so we can now mock and test and actually use MongoDb in our project. Now we need to go and modify the AdminControllerBehaviors test which checks the result of calling the Categories action so that we check to see if there is a view model returned which contains a list of categories. That test now looks like :
[Test] | |
public void When_requesting_the_admin_categories_page_result_is_correct() | |
{ | |
const string expectedViewName = "Categories"; | |
var repository = new Mock<IRepository<Category>>(); | |
repository.Setup(x => x.GetAll()).Returns(new List<Category> | |
{ | |
new Category(), | |
new Category(), | |
new Category() | |
}); | |
var controller = new AdminController(repository.Object); | |
var result = controller.Categories(); | |
result.Should().BeAssignableTo<ActionResult>(); | |
result.AssertViewRendered().ForView(expectedViewName); | |
result.Model.Should().BeAssignableTo<CategoriesListViewModel>(); | |
} |
Now if we run the specflow file now we're moving ahead again except we fail at:
Then I should see "category1" show up in the list of existing categories | |
-> error: Expected True, but found False. |
This is because even though I know we are grabbing the categories from the DB we never actually saved them because we never had a test that demanded it. Now we do so lets extend the repository to add some persistence:
[Test] | |
public void When_saving_a_new_document_document_is_persisted() | |
{ | |
const int expectedCategoryCount = 2; | |
var repository = GetRepository(); | |
repository.Save(new List<Category> | |
{ | |
new Category | |
{ | |
Id = Guid.NewGuid().ToString() | |
}, | |
new Category | |
{ | |
Id = Guid.NewGuid().ToString() | |
} | |
}); | |
var result = repository.GetAll(); | |
result.Count().Should().Be(expectedCategoryCount); | |
} | |
[TearDown] | |
public void TearDown() | |
{ | |
var repository = GetRepository(); | |
repository.GetAll().ForEach(x => repository.Delete(x.Id)); | |
} |
Now we just need to go back and change the CategoriesAdd POST test so that we make sure we save the category into the DB:
[Test] | |
public void When_requesting_the_admin_categories_add_post_page_result_is_correct() | |
{ | |
var repository = new Mock<IRepository<Category>>(); | |
repository.Setup(x=>x.Save(It.IsAny<Category>())).Verifiable(); | |
var controller = new AdminController(repository.Object); | |
var viewModel = new CategoriesAddViewModel(); | |
var result = controller.CategoriesAdd(viewModel); | |
result.Should().BeAssignableTo<RedirectToRouteResult>(); | |
((RedirectToRouteResult)result).RouteValues["Action"].Should().Be("Categories"); | |
((RedirectToRouteResult)result).RouteValues["Controller"].Should().Be("Admin"); | |
repository.VerifyAll(); | |
} |
I've also whizzed through and updated the test for the loading of the categories action so that we are asserting that we are returning some categories:
[Test] | |
public void When_requesting_the_admin_categories_page_result_is_correct() | |
{ | |
const int expectedCategoryCount = 3; | |
const string expectedViewName = "Categories"; | |
var repository = new Mock<IRepository<Category>>(); | |
repository.Setup(x => x.GetAll()).Returns(new List<Category> | |
{ | |
new Category(), | |
new Category(), | |
new Category() | |
}); | |
var controller = new AdminController(repository.Object); | |
var result = controller.Categories(); | |
result.Should().BeAssignableTo<ActionResult>(); | |
result.AssertViewRendered().ForView(expectedViewName); | |
result.Model.Should().BeAssignableTo<CategoriesListViewModel>(); | |
((CategoriesListViewModel) result.Model).Categories.Count.Should().Be(expectedCategoryCount); | |
} |
And the updated CategoriesController now looks like (I'm not suggesting this code is production ready but it passes this test!):
public class AdminController : Controller | |
{ | |
private readonly IRepository<Category> repository; | |
public AdminController(IRepository<Category> repository) | |
{ | |
this.repository = repository; | |
} | |
public ViewResult Categories() | |
{ | |
var categoriesListViewModel = new CategoriesListViewModel(); | |
repository.GetAll().ForEach(x => categoriesListViewModel.Categories.Add(x)); | |
return View("Categories", categoriesListViewModel); | |
} | |
[HttpGet] | |
public ActionResult CategoriesAdd() | |
{ | |
return View("CategoriesAdd"); | |
} | |
[HttpPost] | |
[ActionName("CategoriesAdd")] | |
public ActionResult CategoriesAdd(CategoriesAddViewModel viewModel) | |
{ | |
repository.Save(new Category | |
{ | |
Id = Guid.NewGuid().ToString(), | |
Name = viewModel.CategoryName, | |
Enabled = viewModel.Enabled | |
}); | |
return RedirectToAction("Categories", "Admin"); | |
} | |
} |
I also added the following to the Categories view:
@using BrightonSausageCoEcomms.Infrastructure | |
@model BrightonSausageCoEcomms.Models.CategoriesListViewModel | |
@{ | |
ViewBag.Title = "title"; | |
Layout = "~/Views/Shared/_Layout.cshtml"; | |
} | |
<h2>title</h2> | |
<a href="/admin/categories/add">Add category</a> | |
<ul> | |
@for (int i = 0; i < Model.Categories.Count; i++) | |
{ | |
<li>@Model.Categories[i].Name</li> | |
} | |
</ul> |
Now that we have persistence using MongoDb (which I have never used before) and we have all of the IoC, MVC infrastructure stuff all set up and we can save categories I feel pretty good about the progress. Picking off the categories feature was going easy on myself because it was the easiest feature to drop into. But I also knew that this feature would also bare the brunt of the infrastructure bootstrapping side of stuff. Although this feature isn't 100% complete and the code that is there isn't exactly tight I am happy that it's a good grounding to build on. I'm going to leave it there tonight. Next session I need to complete the feature and make the specflow test pass. I'll also set up a public deployment for the staging code - at some point I should hook this into TeamCity / Octopus deploy but that's further down the line.
Comments
Post a Comment