Wednesday, November 26, 2008

Write My Rhino Mocks Expect Statement

Mocking multiple calls to a complicated external dependency (using RhinoMocks for instance) can be challenging if you want to return realistic data. But if you have access to the external dependency and it has decent test data why can’t your mocking tool just call the real dependency and give you the exact Expect() and Return() statements that you need to mock it’s real state?

Well, now it can with WriteMyExpectStatement on CodePlex. How much would you expect to pay for this power? $1,000? $100? How about absolutely free! Alright, guess I've been watching too much late night TV. Let’s examine the problem via an example.

Example: Mocking a Database

Suppose you have a Northwind style database with products and orders and you want to re-order products whose inventory is low:

public class NorthwindService {
  ...
  public void ReorderLowInventoryProducts() {
    using (SqlCeConnection cn = NorthwindDao.GetConnection()) {
      NorthwindDao.Connection = cn; // simple IOC
      IEnumerable<Product> products = NorthwindDao.GetLowInventoryProducts();
      // cache lookup tables in memory
      Dictionary<int, Supplier> suppliers = NorthwindDao.GetAllSuppliers()
        .ToDictionary(k => k.SupplierID); // yayyyy, LINQ

      foreach (Product product in products) {
        Supplier supplier = suppliers[product.SupplierId];
        supplier.OrderMoreProduct(product);
      }
    }
}

It’s a simple example, but already there is a dependency between the data of the two calls that need to be mocked. Specifically GetLowInventoryProducts() specifies a SupplierId whose value must be returned by GetAllSuppliers(). This isn’t complicated enough you couldn’t mock it yourself, but you can imagine a more complicated example with multiple calls and multiple data dependencies.

RhinoMocks.PleaseJustWriteMyExpectStatementforMe = true

In order to mock the above using Rhino Mocks you would first need some code like the following:

private static void ReorderLowInventoryProductsMocked() {
  NorthwindService northwindService = new NorthwindService();

  MockRepository repository = new MockRepository();
  NorthwindDao northwindDaoMock = repository.StrictMock<NorthwindDao>();
  northwindService.NorthwindDao = northwindDaoMock;

  // put .Expect() statements here

  repository.ReplayAll();

  northwindService.ReorderLowInventoryProducts();
}

And assuming NorthwindDao’s methods are virtual you’ll get

ExpectationViolationException NorthwindDao.GetConnection(); Expected #0, Actual #1.

But if you let WriteMyExpectStatement at it using the following:

try {
  northwindService.ReorderLowInventoryProducts();
} catch (ExpectationViolationException ex) {
  NorthwindDao northwindDaoReal = new NorthwindDao();
  using (SqlCeConnection cn = northwindDaoReal.GetConnection()) {
    northwindDaoReal.Connection = cn;
    MyExpectStatement.Write(ex, "northwindDaoMock", northwindDaoReal);
  }
}

You’ll literally get an exception that looks like this:

NorthwindDao.GetConnection(); Expected #0, Actual #1.
WriteMyExpectStatement:
SqlCeConnection sqlCeConnection = new SqlCeConnection {ConnectionString = "Data Source=Northwind.sdf;Persist Security Info=False;", }
Expect.Call(northwindDaoMock.GetConnection()).Return(sqlCeConnection);

Cool huh? Now if you paste that code at “// Put .Expect() Statements Here” and run it again you’ll get:

NorthwindDao.GetLowInventoryProducts(); Expected #0, Actual #1.
WriteMyExpectStatement:
IEnumerable products = new List {new Product {ReorderLevel = 17, UnitsInStock = 25, ProductName = "Chang", Discontinued = false, ProductID = 2, SupplierId = 1, }, … };
Expect.Call(northwindDaoMock.GetLowInventoryProducts()).Return(products);

Just keep copying and pasting until all of your expect statements are there and you’re done.

Limitations

WriteMyExpectStatement knows how to generate the code to do most things and it will recurse into any complex objects you throw at it (e.g. notice how it picked up the fields in the Product class that it had no a priori knowledge of). What it can’t do as well is call methods that take complicated parameters. For instance through reflection it needs to call GetLowInventoryProducts() on northwindDaoReal. If we abandoned InversionOfControl (IOC) and had SqlCeConnection be a parameter to GetLowInventoryProducts(), then WriteMyExpectStatement would have absolutely no idea that it would need to call SqlCeConnection.Open() and so would fail. So as long as you primarily use primitives as parameters to the things you mock you should be able to use WriteMyExpectStatement. And if you have bigger requirements let me know (“codeplex@l” + “eerichardson.c” + “om”), I’d be happy to let you in to the project.

Summary

I realize most situations don’t really need code generation for their expect statements, but if for instance you have existing integration tests that you want to convert to mocks, then something like this is essential. It was for me anyway.

Wednesday, November 12, 2008

Applications Can't Use SharePoint Master Pages

This is the story of stupid SharePoint problem and an ugly, kludgy, and embarrasing solution. On our project we have the need for an application that looks and feels like a SharePoint subsite. Specifically, we need it to inherit from SharePoint's masterpage. But we also need it to be a separate IIS application.

Problem: SharePoint dislikes Applications

We get the following error when we try to dynamically set the MasterPageFile in code:

System.ArgumentException: The virtual path '/_layouts/application.master' maps to another application, which is not allowed.

Several sites say we just can't do what we're trying to do. We considered a Virtual Path Provider, but decided not to even go down that path because SharePoint's master page undoubtedly has hooks into web.config and httpmodules and such, so we came up with the following hack:

Solution: An Ugly SharePoint Text Manipulation Hack

Deploy a nearly blank page in to /_layouts/ (we do this in our WebAppManifest.xml & WebAppSolution.ddf files). In PageBase (that all pages inherit from) we override Render to retrieve the blank page and do text manipulation to insert our content into it. Here's the code:

protected override void Render(HtmlTextWriter writer) {
  HttpWebRequest request = (HttpWebRequest)WebRequest.Create(GetUrlToBlankPage());
  request.Credentials = CredentialCache.DefaultCredentials;
  using (WebResponse webResponse = request.GetResponse()) {
    StreamReader streamReader = new StreamReader(webResponse.GetResponseStream());
    string pageHostHtml = streamReader.ReadToEnd();

    using (StringWriter stringWriter = new StringWriter())
    using (HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter)) {
      base.Render(htmlTextWriter);
     
      string renderPageHtml = stringWriter.ToString();

      string title = GetTitleFromHtml(renderPageHtml);
      pageHostHtml.Replace("PAGE TITLE", title);

      string content = GetContentFromHtml(renderPageHtml);
     
      pageHostHtml = RemoveFormData(pageHostHtml);
      pageHostHtml = pageHostHtml.Replace("DO NOT MODIFY THIS PAGE", content);
    }
    writer.Write(pageHostHtml);
  }
}

private static string GetContentFromHtml(string html) {
  Match match = Regex.Match(html, "<body>(.*)</body>",
    RegexOptions
.IgnoreCase | RegexOptions.Singleline);
  return match.Success ? match.Groups[1].Captures[0].Value : "";
}

private static string GetTitleFromHtml(string html) {
  Match match = Regex.Match(html, "<title>(.*)</title>", RegexOptions.IgnoreCase);
  return match.Success ? match.Groups[1].Captures[0].Value : "";
}

private static string RemoveFormData(string html) {
  string replaced = Regex.Replace(html, "<form[^>]*>", "",,
    RegexOptions
.IgnoreCase | RegexOptions.Singleline);
  replaced = replaced.Replace("</form>", "");
  return replaced;
}

Conclusion

I’m sure there are plenty of optimizations that we can do (e.g. caching), but that’s the basic idea. But this solution makes me feel so dirty. So please, please, dear reader, tell me there is a better way. I want to feel clean again.

Thursday, November 6, 2008

Eight Miserable TFS Features

I'd prefer to post a positive, happy, or ideally emotion-agnostic technical post, but today Microsoft Visual Studio Team Foundation Server (TFS)'s source control pissed me off one too many times. I could go on for pages, but this list represents the top eight reasons why you should never pick Team Foundation Server for source control (in decreasing order of annoyance).

1. Can't see changes on get latest

If you like to publically humiliate co-workers when they violate coding standards, or even if you just want to keep an eye on what they're doing, then TFS is absolutely not for you. I'm completely spoiled by Tortoise SVN which allows you to

  1. Get latest
  2. See which files have changed; and
  3. Quickly view the diff

I suppose I should be happy TFS at least allows #1.

2. TFS drops Solution Items

Solution Items are files and folders that exist outside of projects, but within solutions. For instance my current project has an etc directory that contains database scripts and such. First of all it would be nice if Visual Studio automatically recognized new items in the etc directory. Ironically you have to "add existing item" even though it already exists. Fine, whatever. But more importantly it would be nice if Visual Studio didn't periodically delete said files from the solution. As convenient of a feature as that may sound, it has caused numerous problems. A "Blame" has determined that nearly every person on the project has activated this feature leading me to believe it is truly a bug. Brilliant.

3. Checked In Files Are Marked Read-only

Suppose you have a file you want to open outside of Visual Studio (gasp). With TFS if you don't already have Visual Studio open you have to start the IDE in order to check the file out to remove the read only bit. So ten minutes later you're ready to go. At least TFS does allow multiple check-out.

4. TFS server name in project file

TFS embeds the server name in every single one of your project files. The beauty of this approach becomes evident when one team member uses an IP address, while another uses the domain name or computer name. You end up fighting over the project files and automatically checking them out when all you want to do is view them.

5. Can't Access Source Control Outside of Visual Studio

Want to check in or get latest without opening Visual Studio? Yea, you can't. Hope you don't have any java (non-Microsoft) code on your project.

6. Reconciling, Painfully Slow

TFS actually does a decent job of auto merging conflicting files. And when it can't auto-merge conflicting files you can view each file side by side and click which change you want to keep. But the process is painful and slow. For instance if you forget to get latest before check in and there are conflicts you can resolve them, but your check in will always fail. Get latest seems to take much longer when there are conflicts. Auto merge is really slow. If there are un-reconcilable conflicts hitting the "resolve" button takes forever and must be done for each and every conflict. It's like it downloads the entire history of the file. And that wait is mandatory even if you simply want to discard either your or the server's changes.

7. *.vsmdi files

This is just a frustrating known bug where if you use TFS for unit testing (Team Foundation Server Test System) and TFS for source control, Visual Studio will either take this .vsmdi file which is essential file for unit testing and either duplicate it or corrupt it. There's a lot of ink spilled on this vsmdi bug and there are some workarounds, but it's a pain.

8. Moving a folder opens all files in the folder

This was the last straw and the impetus for writing this post. Today I wanted to move a "solution item" into a project. I opened the Source control Explorer (which is impossible to find in the first place) and right clicked, selected move, entered my destination, and TFS in its wisdom opened every file in the folder nearly bringing my machine to its knees. There are hundreds of small annoyances like this every day with TFS.

In Summary

TFS actually has some nice features that I like over SVN. Like the merge tool is pretty good, and being able to see all people who are currently working on a file is nice. But overall the cons heavily outweigh the pros. And if it's any indication how bad TFS is for source control (or how much developers hate it) Codeplex has even implemented a SVN to TFS bridge on their server.

It's ridiculous to me that Microsoft uses this tool internally and sells it and people buy it! I must be missing something. If so please post comments to this post. Because the entire thing boggles my mind.