CodeBork | Tales from the Codeface

The coding blog of Alastair Smith, a software developer based in Cambridge, UK. Interested in DevOps, Azure, Kubernetes, .NET Core, and VueJS.


Project maintained by Hosted on GitHub Pages — Theme by mattgraham

One of the reasons progress on my web frameworks’ evaluation has been so slow of late is that my last check-in broke my CCNET automated build, and, as the good programmer I am, I felt that this needed fixing before I made any more progress.

I’d written some unit tests for the PostController (good) that were accessing the database directly (bad), and for whatever reason the build machine couldn’t successfully connect to the database. A classic case of “works on my machine!”, and, as is usually the case with such things, a good example of poor design. First off, my tests were reliant on an external resource to run. This is not a good thing, as it increases the scope for things to go wrong (as I found out to my cost), and in such a way as to reduce the amount of control the developer has over his tests. Doubly bad.

From there, it also pointed to poor design. The PostController knew about the database, intimately. There were methods liberally sprinkled with SQL; the PostController maintained a connection to the database, including a hard-coded connection string. In other words, it was all horribly, horribly wrong.

I quickly came to the conclusion that I needed a Data Abstraction Layer (DAL) that would allow me to mock the database in my unit tests. My first port of call was to investigate Object-Relational Mapping (ORM) solutions. As usual, my trusty Stack Overflow came to the rescue, with a whole host of questions on ORM; “Whose Data Access Layer do you use for .NET” and Best Performing ORM for .NET proved particularly instructive. Frameworks like NHibernate, SubSonic, and Castle ActiveRecord all seemed very good, and particularly feature-rich, but they all seemed far too heavy-weight for my slim and floaty-light blog application. They were all filed under “E” for “experience” and “F” for “future”.

One response to a Stack Overflow question suggested looking into the Repository Pattern, as described by Martin Fowler in his book Patterns of Enterprise Architecture. Like all good design patterns, the Repository Pattern describes a simple solution to a common problem. A repository is provided for each data type stored (e.g., posts, comments, users, etc.), and an interface is defined for each repository providing basic CRUD support. This interface is then implemented for each concrete implementation required. The original pattern seems to provide in-memory and persisted implementations, whereas I only really needed the persisted implementation.

So, I defined my posts’ Repository:

public interface IPostRepository { Post GetPostById(int id); IList GetAllPosts(); bool Save(Post post); bool Delete(Post post); } </blockcode> and implemented this interface on a MySqlPostRepository: public class MySqlPostRepository : IPostRepository { private MySqlConnection db; public MySqlPostRepository() { db = new MySqlConnection(GetConnectionString()); db.Open(); } public Post GetPostById(int id) { var sql = "SELECT * FROM Posts WHERE PostId = ?PostId"; if (id == Post.BLANK_POST.Id) { sql = @"SELECT * FROM Posts WHERE PostId = SELECT MAX(PostId) FROM Posts"; } using (var cmd = new MySqlCommand(sql, db)) { var postIdParam = new MySqlParameter(); postIdParam.ParameterName = "?PostId"; postIdParam.Value = id; cmd.Parameters.Add(postIdParam); using (var results = cmd.ExecuteReader() as MySqlDataReader) { var post = new Post(); if (results.Read()) { post.Id = id; post.Title = (string)results["PostTitle"]; post.Summary = (string)results["PostSummary"]; post.Body = (string)results["PostBody"]; post.Author = (int)results["PostAuthor"]; post.DateStamp = (DateTime)results["PostDateTime"]; return post; } } } return new Post(); } ... } You can see the full source code for MySqlPostRepository in my Trac environment for the project. This re-implementation led to a much nicer set of unit tests for the PostController: [TestFixture] public class PostControllerTests { Mockery mockery; IPostRepository postRepos; private const string REPOS_GET_POST_BY_ID = "GetPostById"; private const string REPOS_SAVE = "Save"; [SetUp] public void SetUp() { mockery = new Mockery(); postRepos = mockery.NewMock(); Post newestPost = new Post(); newestPost.Id = 10; newestPost.Author = 1; newestPost.Title = "Test Post"; newestPost.Summary = "This is a test post."; newestPost.Body = "This is my latest post. It is a test."; newestPost.DateStamp = new DateTime(2009, 1, 24, 17, 2, 4); Post post2 = new Post(); post2.Id = 2; post2.Author = 1; post2.Title = "Post 2"; post2.Summary = "This is another test post."; post2.Body = "This is my second post. It is a test post. It has an ID number of 2."; post2.DateStamp = new DateTime(2008, 08, 3, 18, 54, 42); Stub.On(postRepos).Method(REPOS_GET_POST_BY_ID).With(-1).Will(Return.Value(newestPost)); Stub.On(postRepos).Method(REPOS_GET_POST_BY_ID).With(2).Will(Return.Value(post2)); Stub.On(postRepos).Method(REPOS_SAVE); } ... /// /// More thorough test, that tests the default Post object (postId = -1) /// as a sanity check, and then reads a post from the database. /// [Test] public void TestReadViewData() { var controller = new PostController(postRepos); // Mock Post var result = controller.Read(-1) as ViewResult; var post = (Post)result.ViewData.Model; Assert.AreEqual("Test Post", post.Title); // Stored in the DB result = controller.Read(2) as ViewResult; post = (Post)result.ViewData.Model; Assert.AreEqual(2, post.Id); Assert.AreEqual("Post 2", post.Title); } ... } </blockcode> Sadly, my Save() test is still being re-written as some of the test cases I had are no longer valid with the new architecture. However, the TestReadViewData() test provides a fairly instructive example. Before each test is run, the SetUp() method is called, which creates two Post objects, newestPost and post2. These are "mock" objects for the most recently submitted post and the second post submitted respectively. I use the term "mock" loosely - they're not mock objects in the usual sense of the word (as we'll see shortly), but they are fake posts that we expect to see returned from the mocked IPostRepository. The SetUp() method then creates three stubs on IPostRepository, two for GetPostById() and one for Save(). The stubs expect GetPostById() to be called with the argument -1 (a brand new post that hasn't been saved to the database yet, in which case it should return the most recent post, defined as the post with the highest ID), and with the argument 2 (which should return the specific post with ID = 2). The stub on Save() doesn't do anything at the moment, as per my previous note about the Save() method. As you can see from the example above, the NMock framework produces a very nice syntax for defining stubs and expectations, which I go into more detail about in my previous post on NMock. Note also that there is an element of dependency injection here, specifically constructor injection: the PostController now needs to be passed an IPostRepository at construction. Dependency Injection is a form of inversion of control, and decouples PostController from the IPostRepository implementation, all Good Things™. This allows me to fully test the PostController's interactions with the IPostRepository without requiring a database to be present. What it can't do, unfortunately, is allow me to test the MySqlPostRepository without a database. That might need something more inventive, or just not be part of the CCNET build. My next step is to re-factor the IPostRepository interface into a generic IRepository</code> interface so that I don't need to define a new repository interface for each data type. Luckily, the interface is already generic enough (aside from method names) to allow me to accomplish this quite easily.