Alexander Beletsky's development blog

My profession is engineering

Using Approval Testing for ELMAH.MVC project

During the ELMAH.MVC 2.0 preparation I had a bit challenging task. As I mentioned, I was about to adopt some code from ELMAH.Sandox project. The 2.0 release included some new features, but what was important to me is make sure I keep previous ELMAH.MVC functionality.

In case you are following my blog, you probably read about Approval.Test framework, which I personally like and recommend to my fellow developers. I did some posts, there I tried to show some Approvals benefits. Today, I’ll show some real-life use case.

Installing by NuGet

Last time I played with Approvals, the binaries were only available at SourseForge project page. It is not convenient at all. I was happy to see the tweet from Llewellyn Falco, he mentioned that ApprovalTests are now available on NuGet. So, installation now as easy as:

PM> Install-Package ApprovalTests

Thinking of test

I had to have some kind of assurance, that I will not break existing ELMAH pages after I switch to new ELMAH controller. Basically, I need to have some ‘Master Database’ that would contain all logged errors and I need to grab all possible output that is being generated by ELMAH error log page handler.

This is what perfectly match the ‘Locking Down’ testing strategy. In locking down testing, you try to get all possible system output, and approve it. In my case, I wanted to approve that all HTTP calls to ELMAH pages, like ‘/elmah’, ‘/elmah/stylesheet’, ‘/elmah/rss’ and so on, still work as they worked before, meaning producing the same output as they produced before.

Test implementation

For my ‘do’ step of the the test, I need to collect all possible applications output. I already know all URL’s, so I just fired up the site with ELMAH.MVC 1.3.2 version installed. I configured the site to use XML files for storing the tests, so I can easily copy them before each test run. That made a kind of ‘Master Database’ for me.

For the verification, all I need to have is ApprovalTests.Approvals.Verify().

[UseReporter(typeof(DiffReporter))]
public class ElmahMvcTests
{
 private const string ElmahMvcAppUrl = "http://localhost:49800/elmah";

 [Test]
 public void lock_elmah_mvc_pages()
 {
  // do
  var content = new StringBuilder();            
  var pages = new[] 
      {
       ElmahMvcAppUrl,
       ElmahMvcAppUrl + "/",
       ElmahMvcAppUrl + "/stylesheet",
       ElmahMvcAppUrl + "/rss",
       ElmahMvcAppUrl + "/digestrss",
       ElmahMvcAppUrl + "/detail?id=5dd2a560-c6fd-4847-a6cc-e3e253db5764",
       ElmahMvcAppUrl + "/json?id=5dd2a560-c6fd-4847-a6cc-e3e253db5764",
       ElmahMvcAppUrl + "/xml?id=5dd2a560-c6fd-4847-a6cc-e3e253db5764"
      };

  foreach (var page in pages)
  {
   content.Append(GetContent(page));
  }

  // verify
  ApprovalTests.Approvals.Verify(content.ToString());
 }

The GetContent() is responsible for getting page content. It could be as simple as ApprovalTests.Asp.AspApprovals.GetUrlContents(url), but you might get in the trap, as I did so.

Dealing dynamic data

The trap is called ‘Dynamic Data’. Dynamic data is every dynamic part of you output. In my case, the dynamic part was a server time generated by ELMAH and putting into page footer. Of cause, even if I run the test and approve the results, on a next test run I still have red test, since I have differences in a seconds of timestamps in footer. It’s annoying, but nothing you can do about it.

My solution was simple. Since I actually don’t care about those timestamps at all, I can simple cut it away from output. The rest of the document, including structure and content would remain the same, so testing quality will not be lower.

So, the GetContent() method will look as follows,

private string GetContent(string url)
{
 return RemoveFooter(ApprovalTests.Asp.AspApprovals.GetUrlContents(url));
}

private string RemoveFooter(string content)
{
 var pattern = "<p id=\"Footer\">(.*)</p>";
 return new Regex(pattern).Replace(content, string.Empty);
}

Approving the results

After I had stable test output, I safely approved that test. Approving in terms of ApprovalTest framework, is simple copy of Accepted file into Approved file.

Now the system got into ‘Locked’ state and I’m safe to do the changes. This is something as Llewellyn mentioned 100% test coverage with one test. Indeed, having only one test and spent 10 minutes to create it I covered huge piece of functionality.

Keep that safe

After I applied all my changes, I re-run approval test to see how it works. I was happy to see, that I’m still generating the same output as it was before. It means, the system still functions as it’s expected. All routes works, generated content does not have differences inside. Cool!

As I said earlier, you don’t need to keep the test for all time. In my case, as I completed my refactoring and saw that code still works (and of cause, manually tested the application), it’s OK just to delete approval test. No longer needed.

Approval.Tests are really shines for a scenarios like that.