Sunday, February 26, 2012

Write your own blog engine, part 4

Adding posts

I finally arrive at the "raison d'être" of a blog: adding posts. I want to be able to add posts and have them be returned in the next GET call to the home page. I also want to be able to add posts out of order by explicitly indicating the date/time of the new post (so that it gets sorted somewhere else than the top position).

I'll start with an acceptance test for the first part:

    [TestMethod]
    public void AddingAPostReturnsItOnTheHomePage()
    {
      var guid = Guid.NewGuid();
      var POST_DATA = string.Format("Title={0}&Content=This is a test", guid);

      var cookie = Post("/Account/LogOn", "Username=user&Password=pass");
      Post("/Home/AddPost", POST_DATA, cookie);
      Get();

      var articles = root.SelectNodes("/html/body/article").ToList();
      Assert.IsTrue(articles.Any());
      var topArticle = articles.First();
      var header = topArticle.SelectSingleNode("header");
      var title = header.SelectSingleNode("h2");
      Assert.AreEqual(guid.ToString(), title.InnerText);
    }

The private Post method looks like this:

    private static string Post(string relativeUrl, string data, string cookie = null)
    {
      using (var web = new MyWebClient())
      {
        web.Headers["Content-Type"] = "application/x-www-form-urlencoded";
        if (cookie != null)
          web.Headers[HttpRequestHeader.Cookie] = cookie;

        web.UploadString(BASE_URL + relativeUrl, data);

        return web.ResponseHeaders["Set-Cookie"];
      }
    }

Note that this test is not idempotent; that is, it doesn't leave the system in the same way it found it. Every time this test runs successfully a new post is created. That's why I'll mark it with the [Ignore] attribute after I manage to make it pass.

The test fails with a "The remote server returned an error: (404) Not Found." error because there's no AddPost method on the HomeController. Time to start working on the unit tests, the first of which will verify that new posts are being added to the repository:

    [TestMethod]
    public void POST_AddPostAddsToTheRepository()
    {
      var post = new Post();

      sut.AddPost(post);

      repository.Verify(it => it.AddPost(post));
    }

Of course, this means adding a new method to the PostRepository interface:

  public interface PostRepository
  {
    IEnumerable<Post> GetRecentPosts();
    void AddPost(Post post);
  }

with the corresponding (empty) implementation on the DbPostRepository class:

    public void AddPost(Post post)
    {
    }

and finally the AddPost method on the HomeController class:

    [HttpPost]
    public ActionResult AddPost(Post post)
    {
      return null;
    }

Of course, the unit test fails with an error: "Expected invocation on the mock at least once, but was never performed: it => it.AddPost(.post)". This means I have to change the AddPost method to:

    [HttpPost]
    public ActionResult AddPost(Post post)
    {
      postRepository.AddPost(post);

      return null;
    }

Don't get over-excited and try to write the full implementation of the method. The majority of the lifetime of a project is spent reading and modifying existing code, not writing it for the first time. That means that when you or someone else will come back six months later to make a change to this code you will need a way to make sure that the changes you make don't break features. If you just write the code without tests you won't have that "safety net" to prevent you from making the wrong changes.

Ok, this method is clearly under-specified. What should happen after a post has been successfully added? I decided that the customer (me) will want to see that the new post has been added so I'll return to the home page:

    [TestMethod]
    public void POST_AddPostRedirectsToTheHomePage()
    {
      var result = sut.AddPost(new Post()) as RedirectToRouteResult;

      Assert.AreEqual("Index", result.RouteValues["action"]);
    }

Making it pass is easy:

    [HttpPost]
    public ActionResult AddPost(Post post)
    {
      postRepository.AddPost(post);

      return RedirectToAction("Index");
    }

Good. Time to work on persistence, more precisely on the (falsely named so far) DbPostRepository class. I am going to use the EntityFramework package for this project because I love its code-first features; this package is installed by default by the MVC applications but you should make sure it is up to date: right click the References folder, choose "Manage NuGet packages…", click on Updates on the left side and, if EntityFramework does show up in the list, click Update.

Now that this has been taken care of I will create a new class inheriting from DbContext; this class will be "the database" as far as the code is concerned. Add a new Persistence folder to the MyBlogEngine.MVC project and a new BlogDB class to it:

  public class BlogDB : DbContext
  {
    public DbSet<Post> Posts { get; set; }
  }

The DbPostRepository class will receive an instance of the BlogDB class in its constructor… wait, no it won't, that would break the first rule I decided on: only depend on abstractions, never on concrete classes.

This is called the Dependency Inversion Principle - this article (pdf) has a very detailed explanation of it.

That means I'm going to need an interface; fortunately, EF 4 (at least) was designed to be testable so that's not very difficult to do. Here are the BlogPersistence interface and the updated BlogDB class:

  public interface BlogPersistence
  {
    IDbSet<Post> Posts { get; }

    int SaveChanges();
  }

  public class BlogDB : DbContext, BlogPersistence
  {
    public IDbSet<Post> Posts { get; set; }
  }

Now the DbPostRepository class can require a BlogPersistence argument in its constructor and thus allow testing without actually hitting the database. That's good, let's start by writing some of those tests. The first one should make sure that a fake post will be returned if there are no real posts in the database (so as not to break the second acceptance test), so here is the new DbPostRepositoryTests class:

  [TestClass]
  public class DbPostRepositoryTests
  {
    [TestMethod]
    public void GetRecentPostsReturnsFakePostIfTableIsEmpty()
    {
      var persistence = new Mock<BlogPersistence>();
      var sut = new DbPostRepository(persistence.Object);

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual(1, result.Count);
      Assert.AreEqual("Welcome", result[0].Title);
    }
  }

I'll have to add a constructor to make it compile:

    public DbPostRepository(BlogPersistence blogPersistence)
    {
    }

This in turn breaks down the MunqMvc3Startup class so I'll change the PreStart method:

    public static void PreStart()
    {
      DependencyResolver.SetResolver(new MunqDependencyResolver());

      var ioc = MunqDependencyResolver.Container;
      ioc.Register<BlogPersistence>(c => new BlogDB());
      ioc.Register<PostRepository>(c => new DbPostRepository(c.Resolve<BlogPersistence>()));
      ioc.Register<UserService>(c => new HardcodedUserService());
    }

The test is… passing. Oops. If a new test doesn't start by failing something is wrong; in this particular case the test is passing for the wrong reason - I'm always returning the fake post. I'm tempted to skip over the intermediary step and just write the correct implementation but a bit of discipline can't hurt. I replace the method body with a single return new List<Post>(); line and verify that it is indeed failing (Assert.AreEqual failed. Expected:<1>. Actual:<0>.). Now I can go ahead and write the correct implementation, which requires changing more than just that method:

  public class DbPostRepository : PostRepository
  {
    public DbPostRepository(BlogPersistence blogPersistence)
    {
      this.blogPersistence = blogPersistence;
    }

    public IEnumerable<Post> GetRecentPosts()
    {
      var posts = blogPersistence.Posts.ToList();
      if (!posts.Any())
        posts.Add(GetFakePost());

      return posts;
    }

    public void AddPost(Post post)
    {
    }

    //

    private readonly BlogPersistence blogPersistence;

    private static Post GetFakePost()
    {
      return new Post { CreatedOn = DateTime.Now, Title = "Welcome", Content = "<p>This is a fake post.</p>" };
    }
  }

The test is now… failing. (This stuff is hard.) That's because blogPersistence.Posts is null so the ToList call crashes. Ok, I need to fix the test to return an empty list:

    [TestMethod]
    public void GetRecentPostsReturnsFakePostIfTableIsEmpty()
    {
      var postsTable = new Mock<IDbSet<Post>>();
      postsTable
        .Setup(it => it.GetEnumerator())
        .Returns(new List<Post>().GetEnumerator());
      var persistence = new Mock<BlogPersistence>();
      persistence
        .SetupGet(it => it.Posts)
        .Returns(postsTable.Object);
      var sut = new DbPostRepository(persistence.Object);

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual(1, result.Count);
      Assert.AreEqual("Welcome", result[0].Title);
    }

All of the unit tests are passing now. Whew. However, when I said before that I was writing the correct implementation I was exaggerating a bit. The reason for that is that I'm not restricting the posts being returned in any way: I will happily return hundreds of posts from this method. I'll write a test to expose that problem:

    [TestMethod]
    public void GetRecentPostsOnlyReturnsFive()
    {
      var posts = new List<Post>();
      for (var i = 0; i < 10; i++)
        posts.Add(new Post());
      var postsTable = new Mock<IDbSet<Post>>();
      postsTable
        .Setup(it => it.GetEnumerator())
        .Returns(posts.GetEnumerator());
      var persistence = new Mock<BlogPersistence>();
      persistence
        .SetupGet(it => it.Posts)
        .Returns(postsTable.Object);
      var sut = new DbPostRepository(persistence.Object);

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual(5, result.Count);
    }

This test fails (Assert.AreEqual failed. Expected:<5>. Actual:<10>.). That's ok, I can fix that easily:

    public IEnumerable<Post> GetRecentPosts()
    {
      var posts = blogPersistence.Posts.ToList();
      return posts.Any()
               ? posts.Take(5)
               : new List<Post> { GetFakePost() };
    }

Of course, returning any five posts isn't enough; I need to return the most recent five. That means another test:

    [TestMethod]
    public void GetRecentPostsReturnsTheMostRecentFive()
    {
      var posts = new List<Post>();
      for (var i = 0; i < 10; i++)
        posts.Add(new Post { CreatedOn = new DateTime(2000, 1, i + 1), Title = i.ToString() });
      var postsTable = new Mock<IDbSet<Post>>();
      postsTable
        .Setup(it => it.GetEnumerator())
        .Returns(posts.GetEnumerator());
      var persistence = new Mock<BlogPersistence>();
      persistence
        .SetupGet(it => it.Posts)
        .Returns(postsTable.Object);
      var sut = new DbPostRepository(persistence.Object);

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual("9", result[0].Title);
      Assert.AreEqual("8", result[1].Title);
      Assert.AreEqual("7", result[2].Title);
      Assert.AreEqual("6", result[3].Title);
      Assert.AreEqual("5", result[4].Title);
    }

Again, easy to fix. (One of the important advantages of TDD is that you're supposed to write code in small steps: easy to write test, easy to write code to make it pass. If something is difficult to write it usually indicates a problem, either with the existing design or with your understanding of the domain.)

    public IEnumerable<Post> GetRecentPosts()
    {
      var posts = blogPersistence.Posts.ToList();
      return posts.Any()
               ? posts
                   .OrderByDescending(p => p.CreatedOn)
                   .Take(5)
               : new List<Post> { GetFakePost() };
    }

The new unit test passes too and so do all others. (Not the acceptance tests though - something is wrong there, but it's not yet time to look into that.)

Now that the GetRecentPosts method is fully specified, it's time to look at the test code. There's far too much duplication for my taste. (I initially wrote "test" instead of "taste". Har, har.) The modified DbPostRepositoryTests class looks like this:

  [TestClass]
  public class DbPostRepositoryTests
  {
    [TestInitialize]
    public void SetUp()
    {
      posts = new List<Post>();
      postsTable = new Mock<IDbSet<Post>>();
      persistence = new Mock<BlogPersistence>();
      persistence
        .SetupGet(it => it.Posts)
        .Returns(postsTable.Object);
      sut = new DbPostRepository(persistence.Object);
    }

    [TestMethod]
    public void GetRecentPostsReturnsFakePostIfTableIsEmpty()
    {
      SetUpPosts();

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual(1, result.Count);
      Assert.AreEqual("Welcome", result[0].Title);
    }

    [TestMethod]
    public void GetRecentPostsOnlyReturnsFive()
    {
      for (var i = 0; i < 10; i++)
        posts.Add(new Post());
      SetUpPosts();

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual(5, result.Count);
    }

    [TestMethod]
    public void GetRecentPostsReturnsTheMostRecentFive()
    {
      for (var i = 0; i < 10; i++)
        posts.Add(new Post { CreatedOn = new DateTime(2000, 1, i + 1), Title = i.ToString() });
      SetUpPosts();

      var result = sut.GetRecentPosts().ToList();

      Assert.AreEqual("9", result[0].Title);
      Assert.AreEqual("8", result[1].Title);
      Assert.AreEqual("7", result[2].Title);
      Assert.AreEqual("6", result[3].Title);
      Assert.AreEqual("5", result[4].Title);
    }

    //

    private List<Post> posts;
    private Mock<IDbSet<Post>> postsTable;
    private Mock<BlogPersistence> persistence;
    private DbPostRepository sut;

    private void SetUpPosts()
    {
      postsTable
        .Setup(it => it.GetEnumerator())
        .Returns(posts.GetEnumerator());
    }
  }

All the unit tests are still passing so I didn't break anything.

Time to see what's wrong with the acceptance tests. The easiest way is to try to run the application and I immediately get back an error: "EntityType 'Post' has no key defined." True - EF requires that all entities have a primary key. I've decided to use a GUID as the primary key for the Posts instead of an integer or something else; making this change fixes all the acceptance tests except for the last one:

  public class Post
  {
    public Guid Id { get; set; }
    public DateTime CreatedOn { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
  }

Note that this made EF create the database because I have SQL Express installed and EF will create the database there if it can. This is how the Posts table was created:

CREATE TABLE [dbo].[Posts](
[Id] [uniqueidentifier] NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[Title] [nvarchar](max) NULL,
[Content] [nvarchar](max) NULL,
 CONSTRAINT [PK_Posts] PRIMARY KEY CLUSTERED 
(
[Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

The last acceptance test fails because the DbPostRepository.AddPost method doesn't actually do anything. What it should do is, of course, add the new post to the database so I'll write a test to specify that:

    [TestMethod]
    public void AddPostAddsTheNewPostToTheDatabase()
    {
      var post = new Post();

      sut.AddPost(post);

      postsTable.Verify(it => it.Add(post));
      persistence.Verify(it => it.SaveChanges());
    }

The specification is so detailed that writing the production code is practically automatic:

    public void AddPost(Post post)
    {
      blogPersistence.Posts.Add(post);
      blogPersistence.SaveChanges();
    }

This made the unit test pass but now the acceptance test fails with an internal error. That's because the CreatedOn field was not specified. I should default it to the current date/time so here's a test verifying that:

    [TestMethod]
    public void AddPostDefaultsCreatedOnToCurrentDateTime()
    {
      var post = new Post();

      sut.AddPost(post);

      Assert.AreEqual(DateTime.Now, post.CreatedOn);
    }

Easy to make it pass:

    public void AddPost(Post post)
    {
      if (post.CreatedOn == DateTime.MinValue)
        post.CreatedOn = DateTime.Now;

      blogPersistence.Posts.Add(post);
      blogPersistence.SaveChanges();
    }

Except… the test fails because it takes a bit of time between the end of the AddPost method call and the test and thus the value of DateTime.Now has changed. Well, I didn't like having a dependency on DateTime anyway. Here's how to fix it; change the test to:

    [TestMethod]
    public void AddPostDefaultsCreatedOnToCurrentDateTime()
    {
      var post = new Post();
      var dt = new DateTime(2000, 1, 1);
      sut.Clock = () => dt;

      sut.AddPost(post);

      Assert.AreEqual(dt, post.CreatedOn);
    }

and add this to the beginning of the DbPostRepository class:

    public Func<DateTime> Clock = () => DateTime.Now; 

Of course, the AddPost method must change to use the new Clock function instead of DateTime.Now:

    public void AddPost(Post post)
    {
      if (post.CreatedOn == DateTime.MinValue)
        post.CreatedOn = Clock();

      blogPersistence.Posts.Add(post);
      blogPersistence.SaveChanges();
    }

All the tests pass, including the acceptance tests and, in addition to that, running the application no longer returns the fake post (it does return a test post instead). Unfortunately, looking at the table in the database I discover that the Id of the test post is the empty GUID (all zeroes) and, in fact, if I run the last acceptance test I am now getting an internal error. That means I need to create a new GUID in the AddPost method if the post's key is the empty GUID. I'll start with a test:

    [TestMethod]
    public void AddPostDefaultsTheIdToANewGuid()
    {
      var post = new Post();

      sut.AddPost(post);

      Assert.AreNotEqual(Guid.Empty, post.Id);
    }

I could have gone the same route as before and extract the Guid.NewGuid call into a Func<Guid> delegate but I chose not to. I don't care what value the Id gets as long as it's not the empty GUID.

The final fix to the AddPost method looks like this:

    public void AddPost(Post post)
    {
      if (post.CreatedOn == DateTime.MinValue)
        post.CreatedOn = Clock();
      if (post.Id == Guid.Empty)
        post.Id = Guid.NewGuid();

      blogPersistence.Posts.Add(post);
      blogPersistence.SaveChanges();
    }

All the tests are passing and I can now mark the last acceptance test with the [Ignore] attribute. I will have to make sure to re-run that test once in a while but for the most part I'm satisfied with it being disabled.

I'm still not done, of course: I need a way for the user to add the new post and I need to make sure only authenticated users can do that. Here's the test for the GET call to the AddPost method:

    [TestMethod]
    public void GET_AddPostReturnsView()
    {
      var result = sut.AddPost() as ViewResult;

      Assert.AreEqual("", result.ViewName);
    }

The new method is very simple:

    [HttpGet]
    public ActionResult AddPost()
    {
      return View(new Post());
    }

The view looks like this:

@{
  ViewBag.Title = "New post";
}

@using Renfield.MyBlogEngine.MVC.Models
@model Post

<h2>@ViewBag.Title</h2>

@Html.ValidationSummary()

@using(Html.BeginForm())
{
  <fieldset>
    <legend>Add new post</legend>
    @Html.EditorForModel()

    <input type="submit" value="Submit"/>
  </fieldset>
}

Two things remain to be done: mark the two AddPost method with the [Authorize] attribute so that only authenticated users can call them (non-authenticated users get redirected to the LogOn method) and add a link to the GET AddPost method in the home page view. I just changed the Index view to:

@{
    ViewBag.Title = "MyBlogEngine";
}

@using Renfield.MyBlogEngine.MVC.Models
@model IEnumerable<Post>

<h2>@ViewBag.Title</h2>

@Html.ActionLink("Create new post", "AddPost")

@foreach (var post in Model)
{
  <article>
    <header>
      <p><time pubdate="pubdate">@post.CreatedOn.ToLongDateString()</time></p>
      <h2>@post.Title</h2>
    </header>
    @Html.Raw(post.Content)
  </article>
}

Hmm… I'm still not done. For one thing, clicking on the new "Create new post" link does send me to the LogOn page, but after I log on correctly it sends me back to the home page. That's because I was looking for the return url in the ViewData property and instead it was sent in the query string. I will have to change a test to account for that:

    [TestMethod]
    public void POST_LogOnRedirectsToPreviousPageForValidCredentials()
    {
      const string RETURN_URL = "url";
      var user = new User { Username = "x", Password = "y" };
      var cookies = new HttpCookieCollection();
      var service = new Mock<UserService>();
      service
        .Setup(it => it.ValidateUser(user, cookies))
        .Returns(true);
      var sut = new AccountController(service.Object);
      sut.SetFakeControllerContext();
      sut.Request.SetupRequestUrl("~/?ReturnUrl=" + RETURN_URL);
      sut.Response.SetUpCookies(cookies);

      var result = sut.LogOn(user) as RedirectResult;

      Assert.AreEqual(RETURN_URL, result.Url);
    }

The POST LogOn method changes to:

    [HttpPost]
    public ActionResult LogOn(User user)
    {
      var returnUrl = Request.QueryString["ReturnUrl"];

      if (userService.ValidateUser(user, Response.Cookies))
      {
        return returnUrl != null
                 ? (ActionResult) Redirect(returnUrl)
                 : RedirectToAction("Index", "Home");
      }

      ModelState.AddModelError("", "Invalid user or password");

      return View(user);
    }

That makes the test pass but unfortunately two of the other tests in the AccountControllerTests class fail because QueryString is null. The fix is to change the beginning of the POST LogOn method to

    var returnUrl = Request.QueryString != null ? Request.QueryString["ReturnUrl"] : null;

This makes all the tests pass.

The remaining issue is the AddPost view: the Id field should not be visible and the CreatedOn field should contain the current date/time. Change the Post class like this to fix the first issue:

  public class Post
  {
    [HiddenInput(DisplayValue = false)]
    public Guid Id { get; set; }

    public DateTime CreatedOn { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
  }

The second issue has to be fixed inside the HomeController class, which means a new unit test:

    [TestMethod]
    public void GET_AddPostInitializesTheCreatedOnField()
    {
      var dt = new DateTime(2000, 1, 1);
      sut.Clock = () => dt;

      var result = sut.AddPost() as ViewResult;

      Assert.AreEqual(dt, ((Post) result.Model).CreatedOn);
    }

Just like I did earlier I'll add a Clock delegate to handle the dependency on the current date/time; add this to the beginning of the HomeController class:

    public Func<DateTime> Clock = () => DateTime.Now;

The GET AddPost method changes to:

    [HttpGet]
    [Authorize]
    public ActionResult AddPost()
    {
      return View(new Post { CreatedOn = Clock() });
    }

This makes all the test pass and the /Home/AddPost page shows the current date/time.

The final change I want to make is to change the Content textbox into a textarea (hard to write a whole post in that field). That can be done by changing the Post class again:

  public class Post
  {
    [HiddenInput(DisplayValue = false)]
    public Guid Id { get; set; }

    public DateTime CreatedOn { get; set; }
    public string Title { get; set; }

    [DataType(DataType.MultilineText)]
    public string Content { get; set; }
  }

Finally (!) there's one more change I want to make to the third acceptance test, a refactoring I didn't want to make until I was sure everything else was working:

    [TestMethod]
    public void LoggingInReturnsAuthenticationCookie()
    {
      const string COOKIE_NAME = ".ASPXAUTH";
      const string POST_DATA = "Username=user&Password=pass";

      var cookie = Post("/Account/LogOn", POST_DATA);

      Assert.IsTrue(cookie.StartsWith(COOKIE_NAME));
    }

The aesthetics leave a lot to be desired, as I warned you in the beginning, but at least the functionality is there. I'm happy with what I've accomplished today so I'll end this post now.

Saturday, February 25, 2012

Write your own blog engine, part 3

Authenticating the user

I can't let everyone add posts to my blog; that means having a method to distinguish between a visitor and the owner of the blog. One way to do that would be to check the IP of the incoming connection and verify that it belongs to a list of known IPs; however, that would limit me to a fixed number of computers. What if I'm visiting someone and come up with a new idea for a blog post?

No, the correct way of handling this is to add a login screen and verify that the user and password are correct. I am going to use forms authentication so the result of the login process would be a cookie that would re-authenticate the user on subsequent page views. The process will be:

  • user hits an URL that requires authentication
  • user gets redirected to the /Account/LogOn page (and the previous URL gets saved)
  • user enters credentials and clicks the Submit button
  • the credentials are verified; if they are not valid, the user gets redirected to the home page
  • if the credentials are correct, the user is redirected back to the saved URL which allows him access because he is now authenticated

The new acceptance test will check for the presence of that cookie as a result of a POST call to the /Account/LogOn URL:

    [TestMethod]
    public void LoggingInReturnsAuthenticationCookie()
    {
      const string COOKIE_NAME = ".ASPXAUTH";
      const string POST_DATA = "Username=user&Password=pass";

      using (var web = new WebClient())
      {
        web.Headers["Content-Type"] = "application/x-www-form-urlencoded";
        web.UploadString(BASE_URL + "/Account/LogOn", POST_DATA);
        var cookie = web.ResponseHeaders["Set-Cookie"];
        Assert.IsTrue(cookie.StartsWith(COOKIE_NAME));
      }
    }

For now, the test fails with the error "The remote server returned an error: (404) Not Found." – that's because I haven't created the Account controller. I will do that but, of course, not until I write the AccountControllerTests class:

  [TestClass]
  public class AccountControllerTests
  {
    [TestMethod]
    public void POST_LogOnRedirectsToHomePageForInvalidCredentials()
    {
      var sut = new AccountController();

      var result = sut.LogOn(new User()) as RedirectToRouteResult;

      Assert.AreEqual("Home", result.RouteValues["controller"]);
      Assert.AreEqual("Index", result.RouteValues["action"]);
    }
  }

ReSharper tells me that I need to add a reference to System.Web for this to work, so I do.

Of course, nothing compiles. Time to add the new controller:

  public class AccountController : Controller
  {
    [HttpPost]
    public ActionResult LogOn(User user)
    {
      return null;
    }
  }

Note the use of the [HttpPost] attribute; I need that because I will have a different method called when there's a GET request to that URL.

I also need to write the User class; add it in the Models folder, together with the Post class:

  public class User
  {
    public string Username { get; set; }
    public string Password { get; set; }
  }

The unit test will fail, of course; to make it pass I need to change the LogOn method to:

    [HttpPost]
    public ActionResult LogOn(User user)
    {
      return RedirectToAction("Index", "Home");
    }

While writing this part I had problems with a test that was apparently running forever. Just in case you hit this problem, here's how to change the default timeout (you can stop the stuck test by right-clicking on it and choosing "Stop Test Run"): double-click the Local.testsettings file in the Solution Items folder at the top of the solution tree, click the Test Timeouts section on the left hand side and change the value in the bottom part of the window ("Mark an individual test as failed if its execution time exceeds") to 10 seconds. Click Apply and then Close.

The unit test passes but the acceptance test fails with the weird error "Content-Length or Chunked Encoding cannot be set for an operation that does not write data." Searching for it tells me that's because of the redirect; the page I'm POST-ing to returns a redirect response and the WebClient stupidly tries to follow the redirect. I found a solution here (thanks, Peter) so I'll add a new class to the MyBlogEngine.Tests project:

  public class MyWebClient : WebClient
  {
    protected override WebRequest GetWebRequest(Uri address)
    {
      var wr = base.GetWebRequest(address);
      if (wr is HttpWebRequest)
        (wr as HttpWebRequest).AllowAutoRedirect = false;
      
      return wr;
    }
  }

Replacing new WebClient() with new MyWebClient() in the LoggingInReturnsAuthenticationCookie acceptance test makes it fail with a better error message: "Object reference not set to an instance of an object." That's because no cookie is being returned. Good - back to the unit tests.

Ok, I need a way for the controller to validate the user. I could do that in the controller but I don't like violating the Single Responsibility Principle so I will pass a UserService interface to the controller.

Here's the new unit test:

    [TestMethod]
    public void POST_LogOnRedirectsToPreviousPageForValidCredentials()
    {
      const string RETURN_URL = "url";
      var user = new User { Username = "x", Password = "y" };
      var cookies = new HttpCookieCollection();
      var service = new Mock<UserService>();
      service
        .Setup(it => it.ValidateUser(user, cookies))
        .Returns(true);
      var sut = new AccountController(service.Object);
      sut.ViewData["ReturnUrl"] = RETURN_URL;

      var result = sut.LogOn(user) as RedirectResult;

      Assert.AreEqual(RETURN_URL, result.Url);
    }

This means adding a new UserService interface to the Services folder:

  public interface UserService
  {
    bool ValidateUser(User user, HttpCookieCollection cookies);
  }

and also adding a constructor to the AccountController class:

    public AccountController(UserService userService)
    {
    }

(Change the first unit test in the AccountControllerTests class to pass on a null as the UserService argument so that everything compiles.)

The new unit test fails because the result of the method call is not a RedirectResult. I need to change the whole AccountController class to make it pass:

  public class AccountController : Controller
  {
    public AccountController(UserService userService)
    {
      this.userService = userService;
    }

    [HttpPost]
    public ActionResult LogOn(User user)
    {
      if (userService.ValidateUser(user, Response.Cookies))
        return Redirect((string) ViewData["ResponseUrl"]);

      return RedirectToAction("Index", "Home");
    }

    //

    private readonly UserService userService;
  }

The test fails, unfortunately, because Response is null when running from the test engine (as opposed to running it "for real"). Fortunately, Scott Hanselman came up with a solution: MvcMockHelpers. I've changed his version a bit (and of course I'm only using Moq), so add a new class to the MyBlogEngine.Tests project:

  public static class MvcMockHelpers
  {
    public static HttpContextBase FakeHttpContext()
    {
      var context = new Mock<HttpContextBase>();
      var request = new Mock<HttpRequestBase>();
      var response = new Mock<HttpResponseBase>();
      var session = new Mock<HttpSessionStateBase>();
      var server = new Mock<HttpServerUtilityBase>();

      context.Setup(ctx => ctx.Request).Returns(request.Object);
      context.Setup(ctx => ctx.Response).Returns(response.Object);
      context.Setup(ctx => ctx.Session).Returns(session.Object);
      context.Setup(ctx => ctx.Server).Returns(server.Object);

      return context.Object;
    }

    public static HttpContextBase FakeHttpContext(string url)
    {
      var context = FakeHttpContext();
      context.Request.SetupRequestUrl(url);
      return context;
    }

    public static void SetFakeControllerContext(this Controller controller)
    {
      var httpContext = FakeHttpContext();
      var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
      controller.ControllerContext = context;
    }

    public static void SetHttpMethodResult(this HttpRequestBase request, string httpMethod)
    {
      Mock.Get(request)
        .Setup(req => req.HttpMethod)
        .Returns(httpMethod);
    }

    public static void SetupRequestUrl(this HttpRequestBase request, string url)
    {
      if (url == null)
        throw new ArgumentNullException("url");

      if (!url.StartsWith("~/"))
        throw new ArgumentException("Sorry, we expect a virtual url starting with \"~/\".");

      var mock = Mock.Get(request);

      mock.Setup(req => req.QueryString)
        .Returns(GetQueryStringParameters(url));
      mock.Setup(req => req.AppRelativeCurrentExecutionFilePath)
        .Returns(GetUrlFileName(url));
      mock.Setup(req => req.PathInfo)
        .Returns(string.Empty);
    }

    public static void SetupRequestForm(this HttpRequestBase request, NameValueCollection form)
    {
      Mock.Get(request)
        .Setup(req => req.Form)
        .Returns(form);
    }

    public static void SetupRequestHeaders(this HttpRequestBase request, NameValueCollection headers)
    {
      Mock.Get(request)
        .Setup(req => req.Headers)
        .Returns(headers);
    }

    public static void SetupRequestAcceptTypes(this HttpRequestBase request, IEnumerable<string> acceptTypes)
    {
      Mock.Get(request)
        .Setup(req => req.AcceptTypes)
        .Returns(acceptTypes.ToArray());
    }

    public static void SetUpCookies(this HttpRequestBase request, HttpCookieCollection cookies)
    {
      Mock.Get(request)
        .Setup(req => req.Cookies)
        .Returns(cookies);
    }

    public static void SetUpCookies(this HttpResponseBase request, HttpCookieCollection cookies)
    {
      Mock.Get(request)
        .Setup(req => req.Cookies)
        .Returns(cookies);
    }

    //

    private static NameValueCollection GetQueryStringParameters(string url)
    {
      if (!url.Contains("?"))
        return null;

      var parameters = new NameValueCollection();

      var parts = url.Split("?".ToCharArray());
      var keys = parts[1].Split("&".ToCharArray());

      foreach (var key in keys)
      {
        var part = key.Split("=".ToCharArray());
        parameters.Add(part[0], part[1]);
      }

      return parameters;
    }

    private static string GetUrlFileName(string url)
    {
      return url.Contains("?") ? url.Substring(0, url.IndexOf("?")) : url;
    }
  }

I can now change the unit test to:

    [TestMethod]
    public void POST_LogOnRedirectsToPreviousPageForValidCredentials()
    {
      const string RETURN_URL = "url";
      var user = new User { Username = "x", Password = "y" };
      var cookies = new HttpCookieCollection();
      var service = new Mock<UserService>();
      service
        .Setup(it => it.ValidateUser(user, cookies))
        .Returns(true);
      var sut = new AccountController(service.Object);
      sut.SetFakeControllerContext();
      sut.Response.SetUpCookies(cookies);
      sut.ViewData["ReturnUrl"] = RETURN_URL;

      var result = sut.LogOn(user) as RedirectResult;

      Assert.AreEqual(RETURN_URL, result.Url);
    }

This time it passes. Of course, the other unit test has to change accordingly:

    [TestMethod]
    public void POST_LogOnRedirectsToHomePageForInvalidCredentials()
    {
      var service = new Mock<UserService>();
      var sut = new AccountController(service.Object);
      sut.SetFakeControllerContext();

      var result = sut.LogOn(new User()) as RedirectToRouteResult;

      Assert.AreEqual("Home", result.RouteValues["controller"]);
      Assert.AreEqual("Index", result.RouteValues["action"]);
    }

All the tests pass except for the latest acceptance test. I'm still not done with the unit tests, of course - what happens if ViewData["ReturnUrl"] is null? I decide that I am going to redirect to the home page in that case and add a new unit test to verify that:

    [TestMethod]
    public void POST_LogOnRedirectsToHomePageForValidCredentialsIfNoReturnUrl()
    {
      var user = new User { Username = "x", Password = "y" };
      var cookies = new HttpCookieCollection();
      var service = new Mock<UserService>();
      service
        .Setup(it => it.ValidateUser(user, cookies))
        .Returns(true);
      var sut = new AccountController(service.Object);
      sut.SetFakeControllerContext();
      sut.Response.SetUpCookies(cookies);

      var result = sut.LogOn(user) as RedirectToRouteResult;

      Assert.AreEqual("Home", result.RouteValues["controller"]);
      Assert.AreEqual("Index", result.RouteValues["action"]);
    }

The test fails; I'll change the LogOn method to fix it:

    [HttpPost]
    public ActionResult LogOn(User user)
    {
      var returnUrl = ViewData["ReturnUrl"] as string;

      if (userService.ValidateUser(user, Response.Cookies) && returnUrl != null)
        return Redirect(returnUrl);

      return RedirectToAction("Index", "Home");
    }

The test passes and I broke nothing else. Ok, time to make the acceptance test pass too. That means creating an implementation for the UserService interface. As this is a simple personal blog I won't go into heavy user management here; instead, I will just hardcode a user and password and check against those. Add a new class to the Services folder:

  public class HardcodedUserService : UserService
  {
    public bool ValidateUser(User user, HttpCookieCollection cookies)
    {
      if (user == null)
        return false;

      if (user.Username.ToLowerInvariant() != "user" || user.Password != "pass")
        return false;

      AddAuthenticationCookie(user.Username, cookies);
      return true;
    }

    //

    private static void AddAuthenticationCookie(string username, HttpCookieCollection cookies)
    {
      var authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, "");

      var cookieContents = FormsAuthentication.Encrypt(authTicket);
      var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieContents)
      {
        Expires = authTicket.Expiration,
        Path = FormsAuthentication.FormsCookiePath
      };

      cookies.Add(cookie);
    }
  }

The MunqMvc3Startup.PreStart method has to be modified too:

    public static void PreStart()
    {
      DependencyResolver.SetResolver(new MunqDependencyResolver());

      var ioc = MunqDependencyResolver.Container;
      ioc.Register<PostRepository>(c => new DbPostRepository());
      ioc.Register<UserService>(c => new HardcodedUserService());
    }

All the tests are finally passing. I am not done yet, though: the acceptance test does verify the most important part (the logon POST) but there's still the problem of an actual user trying to log on. For this a GET method will also be required. Here's the corresponding unit test:

    [TestMethod]
    public void GET_LogOnReturnsView()
    {
      var service = new Mock<UserService>();
      var sut = new AccountController(service.Object);

      var result = sut.LogOn() as ViewResult;

      Assert.IsNotNull(result);
      Assert.AreEqual("", result.ViewName);
    }

and here is the matching method on the controller:

    [HttpGet]
    public ActionResult LogOn()
    {
      return View(new User());
    }

Of course, for this to actually work I am going to need a view. Add an Account folder under Views and a LogOn view under that:

@{
  ViewBag.Title = "LogOn";
}

@model object

<h2>@ViewBag.Title</h2>

@using(Html.BeginForm())
{
  <fieldset>
    <legend>Enter credentials</legend>
    @Html.EditorForModel()

    <input type="submit" value="Submit"/>
  </fieldset>
}

If I now go to http://localhost:63516/Account/LogOn I can see the form and test it. However, there is no feedback - both the correct and the wrong credentials will redirect me to the home page. (There's no return URL here.) I should fix that… if the credentials are incorrect I should redisplay the view. That means changing the first unit test to:

    [TestMethod]
    public void POST_LogOnReturnsViewForInvalidCredentials()
    {
      var service = new Mock<UserService>();
      var sut = new AccountController(service.Object);
      sut.SetFakeControllerContext();

      var result = sut.LogOn(new User()) as ViewResult;

      Assert.AreEqual("", result.ViewName);
    }

Change the LogOn method to make this pass:

    [HttpPost]
    public ActionResult LogOn(User user)
    {
      var returnUrl = ViewData["ReturnUrl"] as string;

      if (userService.ValidateUser(user, Response.Cookies))
      {
        return returnUrl != null
                 ? (ActionResult) Redirect(returnUrl)
                 : RedirectToAction("Index", "Home");
      }

      ModelState.AddModelError("", "Invalid user or password");

      return View(user);
    }

I should also change the view to show the error:

@{
  ViewBag.Title = "LogOn";
}

@model object

<h2>@ViewBag.Title</h2>

@Html.ValidationSummary()

@using(Html.BeginForm())
{
  <fieldset>
    <legend>Enter credentials</legend>
    @Html.EditorForModel()

    <input type="submit" value="Submit"/>
  </fieldset>
}

All the tests are passing and I can now see what happens when I enter the incorrect credentials. Good milestone but there's still a lot to do. For one thing, if you press Submit without entering a username the app will crash. That's because of the ToLowerInvariant call on a null reference. That's what I get for not having tests I guess. I'll fix that right away by adding a HardcodedUserServiceTests class:

  [TestClass]
  public class HardcodedUserServiceTests
  {
    [TestMethod]
    public void ValidateUserReturnsFalseForNullUsername()
    {
      var sut = new HardcodedUserService();

      var result = sut.ValidateUser(new User(), new HttpCookieCollection());

      Assert.IsFalse(result);
    }
  }

The test fails, of course; the ValidateUser method has to change to make it pass:

    public bool ValidateUser(User user, HttpCookieCollection cookies)
    {
      if (user == null)
        return false;

      if (user.Username == null || user.Username.ToLowerInvariant() != "user" || user.Password != "pass")
        return false;

      AddAuthenticationCookie(user.Username, cookies);
      return true;
    }

All the tests are passing now and the application is no longer crashing if I click the Submit button without entering a username. Good.

The final thing I want to do now is refactor the AcceptanceTests class; right now, because the GET request is in a method marked with the [TestInitialize] attribute it executes even for the test that actually needs a POST, which is bad. Here is the refactored class:

  [TestClass]
  public class AcceptanceTests
  {
    [TestMethod]
    public void HomePageHasCorrectTitle()
    {
      Get();

      var title = root.SelectSingleNode("/html/head/title").InnerText;
      Assert.AreEqual("MyBlogEngine", title);
    }

    [TestMethod]
    public void HomePageReturnsMostRecentArticles()
    {
      Get();

      var articles = root.SelectNodes("/html/body/article").ToList();
      Assert.IsTrue(articles.Any());
      var topArticle = articles.First();
      var header = topArticle.SelectSingleNode("header");
      Assert.IsNotNull(header);
      var title = header.SelectSingleNode("h2");
      Assert.IsNotNull(title);
      var date = header.SelectSingleNode("//time[@pubdate]");
      Assert.IsNotNull(date);
    }

    [TestMethod]
    public void LoggingInReturnsAuthenticationCookie()
    {
      const string COOKIE_NAME = ".ASPXAUTH";
      const string POST_DATA = "Username=user&Password=pass";

      using (var web = new MyWebClient())
      {
        web.Headers["Content-Type"] = "application/x-www-form-urlencoded";
        web.UploadString(BASE_URL + "/Account/LogOn", POST_DATA);
        var cookie = web.ResponseHeaders["Set-Cookie"];
        Assert.IsTrue(cookie.StartsWith(COOKIE_NAME));
      }
    }

    //

    private const string BASE_URL = "http://localhost:63516/";
    private HtmlDocument doc;
    private HtmlNode root;

    private void Get()
    {
      using (var web = new WebClient())
      {
        var html = web.DownloadString(BASE_URL);

        doc = new HtmlDocument();
        doc.LoadHtml(html);
        root = doc.DocumentNode;
      }
    }
  }

The tests are still passing so I'm satisfied with what I've accomplished so far. Next time I'll work on adding new posts.

Write your own blog engine, part 2

Displaying posts

The next feature I'm going to work on is displaying the most recent five posts. The request isn't specific enough but I will assume that I should display them in descending order – most recent first, then the one before that and so on. I will further assume that each post will be contained inside an article tag and contain a date, title and post content.

You will be tempted to jump in and create a database and start thinking about the necessary fields. As weird as it sounds, it's far too soon. Yes, a database will be needed – the posts are data and they have to be persisted even when the application is not running – but we don't need it just yet. YAGNI, remember? You ain't gonna need it.

YAGNI means a bit more than what it says. It means: don't rush making decisions; you might need this but you might not; wait until the last responsible moment.

It is also tempting to put all the logic required – find the posts somehow, sort them in descending order by date, get the first five posts from the sorted list – inside the home controller. After all, that's the only production class we have so far. (I call them "production classes" by contrast with "test classes".) That can work but is not considered good practice. The single responsibility principle states that every class should have a single responsibility; the default responsibility of controller classes in MVC is to obtain a model object and pass it to a view. Think of it as a dispatcher. That means that figuring out how to obtain the model – in our case, the correct list of five posts – should be the responsibility of another class. Since the first rule I mentioned last time is that dependencies are exposed as interfaces, I will hide this new class behind an interface called PostRepository.

One of the interesting (and painful) things about TDD is that it forces good design – many small classes, many small methods, decoupling and so on. This is a big problem for new practitioners so they quickly abandon it when it seems like too much work. I just encountered a problem: how does the controller know about the repository? It will have to have a private field pointing to it, but how does it get that value? There are (at least) two methods: I could pass it in a public property or I could pass it in the constructor.

This pattern is called "dependency injection" and the two variants above are setter injection and constructor injection. Miško Hevery has a great article about the two styles and why he prefers constructor injection; I do too - property injection leaves open the possibility of incompletely constructed objects.

Time to write the new acceptance test. Because I'm testing the full application from the outside, as a black box, I cannot impose a specific return; all I can do is verify that some articles are being returned. I am going to use the article tag to mark each post, as I said. Here is the code:

    [TestMethod]
    public void HomePageReturnsMostRecentFiveArticles()
    {
      using (var web = new WebClient())
      {
        var html = web.DownloadString(BASE_URL);

        var doc = new HtmlDocument();
        doc.LoadHtml(html);
        var articles = doc.DocumentNode.SelectNodes("/html/body/article").ToList();
        Assert.IsTrue(articles.Any());
        var topArticle = articles.First();
        var header = topArticle.SelectSingleNode("header");
        Assert.IsNotNull(header);
        var title = header.SelectSingleNode("h2");
        Assert.IsNotNull(title);
        var date = header.SelectSingleNode("//time[@pubdate]");
        Assert.IsNotNull(date);
      }
    }

As you can see, there is some duplication in the acceptance tests, duplication that would be easily removed. ("Duplication is the enemy" is my mantra.) However, I will not refactor code if the tests aren't green - it's a good way to mess up everything and have to start over. Just something to keep in mind for after I make the new acceptance test pass.

The test fails because the SelectNodes call returns null, so the ToList() call crashes. That's fine for now; time to start working on the unit tests.

The first unit test should pass a PostRepository instance to the HomeController. Since I don't want to pass the real repository while testing (don't touch the database from the unit tests) I will have to pass a fake repository instead. You can write one by hand but I prefer to use the Moq framework, fortunately available as a NuGet package. Right-click the References folder in the MyBlogEngine.Tests project and choose "Manage NuGet Packages…". Make sure Online/All is selected on the left side and enter "moq" in the top right search box. Click "Install" on the Moq project to add the package to the project.

Here is the code for the unit test:

    [TestMethod]
    public void IndexCallsRepositoryToGetPosts()
    {
      var repository = new Mock<PostRepository>();
      var sut = new HomeController(repository.Object);

      sut.Index();

      repository.Verify(it => it.GetRecentPosts());
    }

Of course, this doesn't compile. Add a Services folder to the MyBlogEngine.MVC project and a new PostRepository interface inside that folder:

  public interface PostRepository
  {
    IEnumerable<Post> GetRecentPosts();
  }

Add a Post class in the Models folder:

  public class Post
  {
  }

As you can see, I'm keeping everything minimal until I actually need to work on it. Right now my focus is on making the code compile; adding properties to the Post class would not help with that goal so I don't do it.

The compiler is still complaining because of the parameter I'm passing to the HomeController constructor; add an empty constructor to silence it:

    public HomeController(PostRepository postRepository)
    {
    }

Now the first unit test is complaining; I'll just pass null to silence it, even though I know that's not going to work. Right now I'm trying to make the tests compile, not pass, so that's fine. By passing null everything compiles so now I can run the new unit test. It fails with the error "Expected invocation on the mock at least once, but was never performed: it => it.GetRecentPosts()". Perfect.

In order to fix this test I'm going to need to do something with the postRepository argument in the HomeController constructor. Here is the complete class:

  public class HomeController : Controller
  {
    public HomeController(PostRepository postRepository)
    {
      this.postRepository = postRepository;
    }

    public ActionResult Index()
    {
      postRepository.GetRecentPosts();

      return View();
    }

    //

    private readonly PostRepository postRepository;
  }

This thing with putting the private stuff at the end, after a separating comment line, is a personal quirk of mine. Feel free to use the more common style of putting the fields at the top of the class, or whatever style you prefer.

The new test passes. Two problems remain: nothing else does, and I "cheated" to make the test pass (I am calling the GetRecentPosts method but I don't do anything with the result). I'll handle the second problem first, by adding a new unit test:

    [TestMethod]
    public void IndexPassesPostsToView()
    {
      var model = new List<Post>();
      var repository = new Mock<PostRepository>();
      repository
        .Setup(it => it.GetRecentPosts())
        .Returns(model);
      var sut = new HomeController(repository.Object);

      var result = sut.Index() as ViewResult;

      Assert.AreEqual(model, result.Model);
    }

This test fails, of course, but that's easily fixed by changing the Index method:

    public ActionResult Index()
    {
      var posts = postRepository.GetRecentPosts();

      return View(posts);
    }

Good, the second problem has been solved, time to work on the first problem. I'll start by handling the first unit test:

    [TestMethod]
    public void IndexReturnsView()
    {
      var repository = new Mock<PostRepository>();
      var sut = new HomeController(repository.Object);

      var result = sut.Index() as ViewResult;

      Assert.IsNotNull(result);
    }

Since the unit tests are all passing now I am allowed to remove the duplication in them. Extract the creation of the repository and sut variables into an initialization method, since they are going to be used for all of the unit tests in the HomeControllerTests class:

  [TestClass]
  public class HomeControllerTests
  {
    [TestInitialize]
    public void SetUp()
    {
      repository = new Mock<PostRepository>();
      sut = new HomeController(repository.Object);
    }
    
    [TestMethod]
    public void IndexReturnsView()
    {
      var result = sut.Index() as ViewResult;

      Assert.IsNotNull(result);
    }

    [TestMethod]
    public void IndexCallsRepositoryToGetPosts()
    {
      sut.Index();

      repository.Verify(it => it.GetRecentPosts());
    }

    [TestMethod]
    public void IndexPassesPostsToView()
    {
      var model = new List<Post>();
      repository
        .Setup(it => it.GetRecentPosts())
        .Returns(model);

      var result = sut.Index() as ViewResult;

      Assert.AreEqual(model, result.Model);
    }

    //

    private Mock<PostRepository> repository;
    private HomeController sut;
  }

The unit tests are still passing so I didn't break anything; good.

The first acceptance test was passing before this change; it is failing now with a (500) Internal Server Error. message. In order to see why this is happening I am going to launch the website with Ctrl-F5 (I don't want to debug it, just see what's going on). I see right away what the problem is: No parameterless constructor defined for this object. Of course, the MVC engine doesn't know how to create a HomeController instance because the only constructor it finds is the one requiring a PostRepository.

I could solve this problem by adding a parameterless constructor that would create an actual implementation of the PostRepository interface; however, I don't yet have an implementation and I don't like the idea anyway (I would just be re-introducing the dependency on a concrete class the interface was meant to avoid). This means that I need to make MVC know how to create my controller. Not as hard as it sounds, fortunately, due to the existence of the Munq library. Right-click the References folder in the MyBlogEngine.MVC project and add the Munq.IocContainer and Munq.MVC3 packages (just like we did before with Moq, except that one was in the test project). This will add an App_Start folder and class; change that class to look like this:

  public static class MunqMvc3Startup
  {
    public static void PreStart()
    {
      DependencyResolver.SetResolver(new MunqDependencyResolver());

      var ioc = MunqDependencyResolver.Container;
      ioc.Register<PostRepository>(c => new DbPostRepository());
    }
  }

Add the DbPostRepository class to the Services folder:

  public class DbPostRepository : PostRepository
  {
    public IEnumerable<Post> GetRecentPosts()
    {
      return new List<Post>();
    }
  }

If I launch the web app now (Ctrl-F5) it starts correctly, so MVC has been able to create the home controller. Good. Running the tests shows me that the first acceptance test is also passing, leaving only the second one. Since I don't have any posts in a database, I will fake this for now by hard-coding one. Change the DbPostRepository.GetRecentPosts method to:

    public IEnumerable<Post> GetRecentPosts()
    {
      return new List<Post>
      {
        new Post { CreatedOn = DateTime.Now, Title = "Welcome", Content = "<p>This is a fake post.</p>" }
      };
    }

I've added some properties to the Post class:

  public class Post
  {
    public DateTime CreatedOn { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
  }

Finally, I need to do something to actually display the posts returned by the repository. Change the Index view to:

@{
    ViewBag.Title = "MyBlogEngine";
}

@using Renfield.MyBlogEngine.MVC.Models
@model IEnumerable<Post>

<h2>@ViewBag.Title</h2>

@foreach (var post in Model)
{
  <article>
    <header>
      <p><time pubdate="pubdate">@post.CreatedOn.ToLongDateString()</time></p>
      <h2>@post.Title</h2>
    </header>
    @Html.Raw(post.Content)
  </article>
}

All the tests are passing now, including the latest acceptance test; running the application allows me to verify that it is indeed working correctly (even though it looks very bland, as I've warned you from the start).

There's still one thing left to do: remove the duplication from the acceptance tests. Here is the modified class:

  [TestClass]
  public class AcceptanceTests
  {
    [TestInitialize]
    public void SetUp()
    {
      using (var web = new WebClient())
      {
        var html = web.DownloadString(BASE_URL);

        doc = new HtmlDocument();
        doc.LoadHtml(html);
        root = doc.DocumentNode;
      }
    }

    [TestMethod]
    public void HomePageHasCorrectTitle()
    {
      var title = root.SelectSingleNode("/html/head/title").InnerText;
      Assert.AreEqual("MyBlogEngine", title);
    }

    [TestMethod]
    public void HomePageReturnsMostRecentArticles()
    {
      var articles = root.SelectNodes("/html/body/article").ToList();
      Assert.IsTrue(articles.Any());
      var topArticle = articles.First();
      var header = topArticle.SelectSingleNode("header");
      Assert.IsNotNull(header);
      var title = header.SelectSingleNode("h2");
      Assert.IsNotNull(title);
      var date = header.SelectSingleNode("//time[@pubdate]");
      Assert.IsNotNull(date);
    }

    //

    private const string BASE_URL = "http://localhost:63516/";
    private HtmlDocument doc;
    private HtmlNode root;
  }

I have removed the word "five" from the name of the second acceptance test because that is not the proper place for it; I will have to make sure I'm limiting the number of posts returned in the DbPostRepository class.

I will attack the problem of authenticating the user in the next post.

Edit I do not normally use Internet Explorer, so it took me a while to discover that IE doesn't apply styles to tags it doesn't know about (and it doesn't know about HTML5, or at least not most versions actually used when I write this). That means you won't be able to make the views look better by changing the Site.css file, or at least you'll have problems with that. Fortunately, the same article told me what to do: just change the _Layout.cshtml file to this:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"> </script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"> </script>
    <script src="//html5shiv.googlecode.com/svn/trunk/html5.js" type="text/javascript"> </script>
  </head>

  <body>
    @RenderBody()
  </body>
</html>

Again, I am not normally using IE so don't take my word for it, verify that it does what it's supposed to do. I am not trying to create a pretty blog engine - I'm not even focusing on creating a blog engine, actually; my goal here is to use a blog engine as a pretext for showing how to apply TDD to a real project.

Wednesday, February 22, 2012

Write your own blog engine, part 1

Introduction

I've decided that I need to write another book about TDD programming. The project I've chosen is a blogging engine, per Rob Conery's suggestion - it should be simple enough that I don't get bogged down in details, but complex enough not to be perceived as a toy object.

What is the minimal feature set? I've decided to start with these features:

  • Display the blog title
  • Display the most recent five posts - date, title and content
  • Login (especially since it's required by the next features)
  • Add a new post
  • Delete an existing post
I will develop this blog engine using ASP.NET MVC 3 and the tests will use the MSTest framework. Start by creating a solution with two projects, an empty MVC 3 application (I've called it MyBlogEngine.MVC) and a test project (MyBlogEngine.Tests). Make sure to add a reference to the blog engine from the test project.

I will try to make sure the design of the application stays clean at all times. In particular:

  1. All dependencies between classes will be passed in constructors
  2. All dependencies that are services ("injectables", in Misko Hevery's terminology) will be exposed as interfaces

Coding, more precisely designing code, is always a matter of choices. Given that I'm using TDD I'll try to keep as simple as possible, following YAGNI (You Ain't Gonna Need It) until I am sure I actually need it. I’ve read on Jeff Atwood’s blog about someone who started this project – writing his own blog engine – five times; each time he got sidetracked by “frameworkitis”, the attempt to develop frameworks that might be useful for every feature imaginable... while never actually getting as far as posting an article. I will attempt not to get bitten by the same problem.

I will confess to being completely dead to pleasant (visible) design; you will need to "prettify" the results yourself, but the functionality should work as expected. I will create a distinct class for acceptance tests and write these tests to exercise the complete application, end to end. Since that means manipulating the resulting HTML I will use the HTML Agility pack to make it easier; right-click the References folder in the MyBlogEngine.Tests project, choose "Manage NuGet Packages...", select Online / All on the left hand side and then enter "agility" in the search box in the top right. Select the HtmlAgilityPack package from the list and click Install.

That being said, I'll start with the first feature: displaying the blog title. Create an AcceptanceTests class and copy / type in the following code:

[TestClass]
public class AcceptanceTests
{
  private const string BASE_URL = "http://localhost:63516/";

  [TestMethod]
  public void HomePageHasCorrectTitle()
  {
    using (var web = new WebClient())
    {
      var html = web.DownloadString(BASE_URL);

      var doc = new HtmlDocument();
      doc.LoadHtml(html);
      var title = doc.DocumentNode.SelectSingleNode("/html/head/title").InnerText;
      Assert.AreEqual("MyBlogEngine", title);
    }
  }
}

I am using ReSharper; it makes my life easier. In particular, it tells me that I need to add a reference to System.Xml and the following using clauses:

using System.Net;
using HtmlAgilityPack;
using Microsoft.VisualStudio.TestTools.UnitTesting;

I will not specify the required usings each time; I hope that won't be too big of a problem.

Press F5 to launch the MVC application but then go back to Visual Studio and stop the debugger. Also, the "63516" port shown above is the port my Visual Studio decided to listen to; either change yours to match or change the code.

Ok, time to run the test. You can use Ctrl-R, A to run all the tests in the solution or you can click on the corresponding button in the tests toolbar. The test fails with the error "The remote server returned an error: (404) Not Found." This is good - I haven't created the home controller yet so it should error out. I should fix this, but I don't want to write production code without unit tests to guard it.

Unit tests, in contrast to acceptance tests:

  1. Only test a small portion of the code (usually a method)
  2. Do not access the network, the database or really anything except the class being tested.

Per MVC conventions, the home page is returned by the method Index in the HomeController class so add a new HomeControllerTests class to the test project:

[TestClass]
public class HomeControllerTests
{
}

The first test should make sure that the "/" page returns something (note that you will need to add a reference to System.Web.Mvc from the test project):

[TestMethod]
public void IndexReturnsView()
{
  var sut = new HomeController();

  var result = sut.Index() as ViewResult;

  Assert.IsNotNull(result);
}

In order to make the project compile I'll need to add the controller. Right-click on the Controllers folder in the MyBlogEngine.MVC project and select Add / Controller. Type "Home" (so that the name of the controller is HomeController) and choose the "Empty Controller" template. Delete the extra crud that Visual Studio insists on adding and leave the class looking like this:

public class HomeController : Controller
{
  public ActionResult Index()
  {
    return View();
  }
}

Now go back to the unit test, add the required using clause to make it pass and run the tests again. Note that the unit test passes (the method does return a view) but the acceptance test fails, this time with "The remote server returned an error: (500) Internal Server Error." That's because when the application tried to render the view, it couldn't find it. Fixing that problem is easy: create a new folder under Views (called Home) and then add a new Index view to it. Leave the contents of the view unchanged and re-run the tests.

The acceptance test is still failing but this time with a much better error message: "Expected:<MyBlogEngine>. Actual:<Index>." All that remains to be done is to fix this; change the contents of the Index view to:

@{
    ViewBag.Title = "MyBlogEngine";
}

<h2>@ViewBag.Title</h2>

This time all the tests pass. We've reached the first milestone!

Wednesday, February 01, 2012

Taking a walk

[Inspired by http://lifeistheteacher.wordpress.com/2012/01/07/my-only-experience-in-a-public-high-school-and-a-call-to-action/]

Frank was walking around, window-shopping while waiting for the time to come to perform at the near middle school. He was a sixteen-year old guitarist who was visiting a friend and got an offer to perform on a Friday in the friend's class. Frank was homeschooled so classes bored him to tears; he preferred to discover what was interesting in this part of the town instead of listening to a teacher drone about a subject he had absolutely no interest in.

Unfortunately, close to the time he was actually preparing to go back to the school, he got stopped by two cops who started asking questions about who he was and what he was doing there. It was clear they believed him to be a truant student but even so, Frank thought, why was it their business?

"I'm a musician; I'm performing at the school here in about half an hour."

"Right, kid, pull the other one," one of the cops said.

Frank was surprised at the disrespect. "Why do you assume I'm lying? And why is it your business anyway?"

"Kid, if I want your opinion I'll give it to you. Where do you live?"

Frank narrowed his eyes. He was starting to get pissed off. "I don't like how you guys behave. Is this your understanding of ‘protect and serve'? I'm a citizen minding my own business; please mind your own." Frank was not going to start anything, but if they annoyed him too much he was going to defend himself.

"Kid," the second cop said, "I think you need some time in jail to cool down. Put your hands behind your head and turn around."

Frank ignored his order. "So you think that might makes right, huh? You can force me to do what you want and that makes it ok? If I could kill you, would that be ok too?"

Both cops put their hands on their guns. "Is that a threat?"

Frank blinked. "Wow, you're stupid even for a cop. Yes, it's a threat."

"That's it!" one of them barked, pulling out his gun. "Hands behind your head, now; you're under arrest."

Frank ignored him once again. "Last chance; cool down and leave or this is the last time you're bullying anyone." He could feel the energy gathering around him and knew that his eyes would soon start to glow. From the descriptions of those who really knew him and what he could do, he knew that would look awesome and scary at the same time.

One of the cops grabbed his shoulder and tried to turn him around. Frank turned his head to look at him and touched him back. Everything went nuts as the cop's arm burst into flames.

The other cop had not pulled his gun before; he did now and shot at Frank. Frank switched his attention from the first cop, who was now screaming his throat raw, and started moving towards the second. The cop kept shooting and backing off, throwing his gun at Frank when the slide locked back. "What are you?" he screamed.

"A kid, I think you called me," Frank replied calmly, still moving towards him. "I just hate bullies. Have you learnt your lesson? Are you going to stop annoying peaceful people? On second thought," he continued without waiting for a reply, "I don't think I trust you." He pointed his finger at the cop who started screaming in fear, expecting to share his colleague's fate. "Might does not make right," he thundered, "but I'm not against using one's opinions against him. Don't make me come after you."

With that, Frank disappeared. A few seconds later, the fire that had by now engulfed the first cop completely went out and his skin started to heal. He was back to normal in less than a minute.