Tuesday, September 21, 2010

Decoupling tests with .NET 4

Recently, I was struggling with an annoying smell in some tests I was writing and found a way to use optional and default parameters to decouple my tests from the object under test's constructor.  Not too long ago, Rob Conery wrote about using C#'s dynamic keyword to do all kinds of weird stuff.  When I was running into these issues with those tests, I took a look at what he'd been playing with.  Nothing there jumped out, but it lead to the optional parameters.

Specifically, I was TDDing an object that had many dependencies injected through the constructor.  Basically what happened was each new test introduced a new dependency, which causes the previous tests to have to be updated.  For example:
[Test]
public void Test1()
{
  var testobj = new Testobj( new Mock<ISomething>().Object );
}
I'm using moq here, and creating a default partial mock, which takes care of itself.

Then I write the next test:
[Test]
public void Test2()
{
  var setupMock = new Mock<ISomething>();
  // setup the mock
  var testobj = new Testobj( setupMock.Object, new Mock<ISomethingElse>().Object );
}
Notice this test has introduced a new dependency, ISomethingElse.  Now the first test wont compile, we have to go update it and add the mock for ISomethingElse.  This will continue with each test that introduces a new dependency causing every previous test to be updated.

You could simply refactor the constructor into a helper method so you only have to change it in one place.  But this doesn't work so well when the tests are passing in their own mocks.  You'd need lots of helper methods with lots of different method overloads.  Enter optional and default parameters!
public Testobj BuildTestobj(Mock<ISomething> something = null, Mock<ISomethingElse> somethingElse = null )
{
  return new Testobj(
    ( something ?? new Mock<ISomething>() ).Object,
    ( somethingElse ?? new Mock<ISomethingElse>() ).Object );
}
Now we can update the tests:
[Test]
public void Test()
{
  var testobj = BuildTestobj();
}

[Test]
public void Test2()
{
  var setupMock = new Mock<ISomething>();
  // setup the mock
  var testobj = BuildTestobj( something = setupMock ); }
Simple, clean, refactor friendly, and your tests are now nicely decoupled from the constructor's method signature!