Posts Tagged Lt
BDD with SpecFlow, NUnit and MVC3–More View a List Of Products
Following on from this post where it looked like BDD was a spectacularly inefficient way to drive out code, I’ll add a new feature to the scenario:
Scenario: Display 10 products
Given There are 10 products
When the product controller is told to display the default view
Then the view should contain a list of 10 products
Compiling and running the tests provides templates for the Given and the Then methods, we get to reuse the existing When method. This reuse is one of the big daws for BDD for me (plus the readability).
To write the method GivenThereAre10Products I need to tell the controller there are ten products. The simplest thing to do is to pass 10 as an argument to the controller constructor then write a loop to populate the product list. I need to move the controller out of WhenTheProductControllerIsToldToDisplayTheDefaultView and declare it as a class field, then re new it in GivenThereAre10Products:
private ProductController controller = new ProductController(0);
[Given(@"There are 10 products")]
public void GivenThereAre10Products() {
controller = new ProductController(10);
}
[When(@"the product controller is told to display the default view")]
public void WhenTheProductControllerIsToldToDisplayTheDefaultView() {
_result = controller.Index();
}
[Then(@"the view should contain a list of 10 products")]
public void ThenTheViewShouldContainAListOf10Products() {
var products = (List<Product>) ViewResult.ViewData.Model;
products.Count.ShouldEqual(10);
}
I can then write the code to make these tests compile then pass, the relevant changes to the controller are:
private readonly int _productsCount;
public ProductController(int productsCount) {
_productsCount = productsCount;
}
public ActionResult Index() {
ViewData["Title"] = "Products";
var products = new List<Product>();
var product = new Product();
for (int i = 0; i < _productsCount; i++) {
products.Add(product);
}
return View(products);
}
This makes the tests pass but needs refactoring. As is fairly standard practise I will refactor to pass a repository into the controller and the repository will be passed a context which will, in this instance fake the database connection. I will detail the repository and fake context in a separate post, but I include a method on the fake repository to add a number of an entity to help testing.
I need to refactor the specs to create a controller with a repository and the relevant refactored code is:
private ProductController _controller;
private ActionResult _result;
public ViewAListOfProducts() {
CreateController(0);
}
private void CreateController(int productCount) {
var context = new FakeRavenContext();
context.AddProducts(productCount);
_controller = new ProductController(new ProductRepository(context));
}
[Given(@"There are 10 products")]
public void GivenThereAre10Products() {
CreateController(10);
}
And the refactored code from the controller:
private readonly IProductRepository _repository;
public ProductController(IProductRepository repository) {
_repository = repository;
}
public ActionResult Index() {
ViewData["Title"] = "Products";
return View(_repository.All());
}
The final thing I want to do here is to add a scenario for 0 products:
Scenario: Display 0 products
Given There are 0 products
When the product controller is told to display the default view
Then the view should contain a list of 0 products
This is where SpecFlow begins to shine, I just need to parameterise 2 of the methods to implement this:
[Given(@"There are 10 products")]
public void GivenThereAre10Products() {
CreateController(10);
}
[Then(@"the view should contain a list of 10 products")]
public void ThenTheViewShouldContainAListOf10Products() {
var products = (List<Product>) ViewResult.ViewData.Model;
products.Count.ShouldEqual(10);
}
Becomes:
[Given(@"There are (.*) products")]
public void GivenThereAreXProducts(int productCount) {
CreateController(productCount);
}
[Then(@"the view should contain a list of (.*) products")]
public void ThenTheViewShouldContainAListOfXProducts(int productCount) {
var products = (List<Product>) ViewResult.ViewData.Model;
products.Count.ShouldEqual(productCount);
}
I still need to implement paging for this scenario then create the view details, add, edit, and delete product scenarios which should drive the design of the product class but I think the BDD side will be pretty much more of the same.
I expect coding with BDD will be at least as fast as TDD but I suspect I will still write some unit tests to complement the BDD specs.
Adding Namespaces to MVC3 Views
Currently there are two ways to add an assemblies namespace to MVC3 views,either in the web.config or with @using.
When adding to the web.config, add the namespace to the web.config in the Veiws/Shared directory. Adding to the web.config in the project root doesn’t work, not sure if this is a bug or working as intended.
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="Raven.Web.Helpers" />
</namespaces>
</pages>
My preference is to add a @using to the view, either on the master layout page a nested layout page or the view itself. It seems more natural to me to have it at the top of the page it is required as that is what I’m used to in code files.
Entity Framework 4 and SQLCE 4 CTPs Odds and Ends
There are already plenty of good walk through for using Code First with EF 4 CTP so this a mop of of some odds and ends I found useful. I’l update this post as further releases are made / I stumble across discover new features.
Adding the NuPack package
EF4 (currently at CTP2) is available as a NuPack package, soon to be NuGet. EFCTP4 can be added as a package by itself or the SQLCE.EntityFramework package can be added to a project and this will include EF4CTP as a dependency. This is my preferred root as I find it easier to develop with SQLCE then swap databases after most of the initial development is complete.
Presuming the solution has a separate project for a data layer, then that is the project the NuPack should be added to. The system.data element should then be copied from the web.config of the data layer (where the Nupack added it) to the web.config of the web front end.
Viewing the SQLCE 4 Database
The SQLCE tooling for VS 2010 and SSMS is yet to be released. WebMatrix alows some examination of SQL CE 4 database files, alternatively basic info can be viewed in VS2010 using SQL Server Compact Toolbox which is a VS addin that can be downloaded from the extension gallery.
Reseeding the database for each run
When developing it is useful to have the database in a known condition each time it is run. When the SQLCE Entity Framework package installed it added a AppStart_SQLCEEntityFramework class. We can use the start method of this class to set an initializer for the database:
Database.SetInitializer(new RavenInitializer());
We can then specify an initializer to recreate the database every time the app is started and prepopulate it:
public class RavenInitializer : AlwaysRecreateDatabase<RavenContext> {
#region Instance Methods
protected override void Seed(RavenContext context) {
var tenants = new List<Tenant>
{
new Tenant
{
Id = 1,
Name = "Tenant 1",
PaidupUntil = DateTime.Now.AddMonths(1),
SubscriptionLevel = 1
},
new Tenant
{
Id = 2,
Name = "Tenant 2",
PaidupUntil = DateTime.Now.AddMonths(-1),
SubscriptionLevel = 2
}
};
tenants.ForEach(t => context.Tenants.Add(t));
}
#endregion
}
Conventions
Primary Key
If a class has a field called either Id or <class name>Id then that field will be used as a primary key.
Named Connection String
If a connection string exists in Web.Config with the same name as the context, EF4 will use it in preference to the standard db factory (see below for an example).
Coming Soon (as of CTP2)
The following are features that are not in CTP2 but are documented in various blogs.
Type Discovery
Currently, if EntityA has a property of type EntityB, EntityB will not persist to the database unless it has a DbSet exposed by the context or is registered as a complex type with the ModelBuilder. In a future release EntityB will be persisted automatically.
Bug Fix for ‘The database File already Exists’
There is currently an issue with EF4 CTP2, it attempts to recreate your SQLCE database every run resulting in an exception being thrown. The 2 workarounds for this are either to revert to CTP1 or specify a connection string in the web.config and remove the DataDirectory macro.
<connectionStrings>
<add name="RavenContext"
connectionString="Data Source=Raven.sdf"
providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>
Not ideal but hopefully this will be fixed for the next release and we can go back to “Data Source=|DataDirectory|Raven.sdf”