CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Karl Seguin

.NET From Ottawa, Ontario - http://twitter.com/karlseguin/

Simplicity is key to successful unit testing

As of late, I've been spending a lot of time writing unit tests for a complex application. In doing so, I've also been working hard to refine my skill of writing effective unit tests. I realize that to make my tests robust, I have to keep my unit tests as well as the code under tests as simple as possible. This also led to a higher quality in code coverage.

Robustness is vitally important for unit testing to be worthwhile. If you make a change in your code which breaks seemingly unrelated tests, you'll end up spending too much time maintaining your tests to get any real value from them. It's critical that you test as granular a behavior as possible with the minimum possible amount of coupling.

Here's a contrived example that exaggerates a bad test (we'll move on to more practical and subtle examples next).

[Test]
public void SetBirthdayUpdatesAge()
{
  var user = new User();
  user.BirthDay = DateTime.Now.AddYears(-17);
  Assert.AreEqual(17, user.Age);
  Assert.IsFalse(user.CanBuyBooze());
}

The last assert might seem harmless – why not verify other expected states, right? The problem is that when we change the logic of the CanBuyBooze() method, our test, which focuses on the behavior of setting a user's age, breaks. In other words, what appears to be adding value to our tests is actually making it brittle. The solution is to add another test (or maybe more than one) to specifically cover the CanBuyBooze() method.

The same issue exists in a far more subtle manner – interaction testing. Look at this example:

[Test]
public void Login_SetsUserInViewData()
{
  var foundUser = new User();
  var repository = Mock.StrictMock<IRepository>();
  repository.Expect(r => r.LoadUser("abc", "123")).Return(foundUser);
  
  var controller = new LoginController();
  controller.Index("abc", "123");
  
  Assert.AreSame(foundUser, controller.ViewData["User"]);

  repository.VerifyAllExpectations();
}

This is probably how I would have written unit tests in the past. It isn't horrible, but it's more brittle than it has to be (the rot will simply get worse for a more complex scenario). If you haven't spotted the problem, ask yourself two simple questions:

  1. What is the purpose of the test?
  2. What does the test actually do?

The purpose is stated plainly enough in the method name – verify that the appropriate user is loaded in the controller's ViewData. But that isn't all we're testing, we're also setting expectations on how we'll interact with our repository. Here's a revised version:

[Test]
public void Login_SetsFoundUserInViewData()
{
  var foundUser = new User();
  var repository = Mock.DynamicMock<IRepository>();
  repository.Stub(r => r.LoadUser(null, null)).IgnoreArguments().Return(foundUser);
  
  var controller = new LoginController();
  controller.Index("abc", "123");
  
  Assert.AreSame(foundUser, controller.ViewData["User"]);
}

There are two key differences. First, rather than using a strict mock we're using a dynamic mock. Second, we've specified IgnoreArguments(). The difference between a strict mock and a dynamic mock is important. A strict mock will fail if members were called which weren't explicitly stated. A dynamic mock is far more forgiving. Secondly, we're using IgnoreArguments() because verifying the inputs into our repository isn't the purpose of this test.

Our new unit test is considerably more robust. You might be thinking that none of this is very pragmatic, but let me give you a simple example. Here's what our initial Login method looked like:

public void Index(string userName, string password)
{
  ViewData["User"] = repository.LoadUserFromCredentials(userName, password);
}

With this code, both tests will work just fine. Now let's make a realistic change:

public void Index(string userName, string password)
{
  passsword = repository.EncryptPassword(password); 
  ViewData["User"] = repository.LoadUserFromCredentials(userName, password);
}

Our initial unit test will fail, while our revised one will continue to work. All we want to do is test that the returned value from our repository is stored in the ViewData – that should require a minimal amount of configuration. If you're writing such test and find yourself setting up multiple expectations (possibly on multiple mock objects) consider rethinking the purpose of your specific test.

There is a place for our strict mock and our explicit argument list – but it isn't within this test. It's for another test (or group of tests) specifically tailored and meant to test the interaction between our controller and repository.



Comments

Ryan Gray said:

This is kind of an off-topic question, but where is the repository getting injected into the LoginController? Are you doing some kind of automocking that's hidden behind the static Mock class?

# September 26, 2008 10:36 AM

Kevin Gao said:

Good examples. Granularity is important for unit test cases. Or else, your cases are too easy to be broken.

# September 26, 2008 11:40 AM

karl said:

@Ryan:

Good question. I left those details out on purpose for simplicity. In the last 2 examples (the controller code) I probably should have renamed repository to _repository so that it's a bit more clear its being loaded externally to the method (possibly through injection). In real life, I'd probably do:

XXXRepository.CreateInstance()  which would call out to StructureMap (perhaps through additional abstractions).

As for the unit test, I use a BaseFixture class that has methods like:

       protected T Dynamic<T>()

       {

           var mock = Mocks.DynamicMock<T>();

           if (typeof(T).IsInterface)

           {

               ObjectFactory.InjectStub(typeof (T), mock);

           }

           return mock;

       }

# September 26, 2008 12:43 PM

Jeremy D. Miller said:

The "ShouldEqual(xxx)" and ShouldBeTheSameAs(xxx) extension methods from SpecUnit help make unit tests simpler and easier to read as well.

For what you're doing in your BaseFixture, you might just use the RhinoAutoMocker<CLASSUNDERTEST> class in StructureMap, but it does assume that you're using DI for the CLASSUNDERTEST

# September 26, 2008 1:24 PM

Dew Drop - September 27, 2008 | Alvin Ashcraft's Morning Dew said:

Pingback from  Dew Drop - September 27, 2008 | Alvin Ashcraft's Morning Dew

# September 27, 2008 7:57 AM

Arjan`s World » LINKBLOG for September 27, 2008 said:

Pingback from  Arjan`s World    &raquo; LINKBLOG for September 27, 2008

# September 27, 2008 5:07 PM

2008 September 29 - Links for today « My (almost) Daily Links said:

Pingback from  2008 September 29 - Links for today &laquo; My (almost) Daily Links

# September 29, 2008 2:52 AM

Reflective Perspective - Chris Alcock » The Morning Brew # 189 said:

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew # 189

# September 29, 2008 3:11 AM

links for 2008-09-29 | the markfr ditherings said:

Pingback from  links for 2008-09-29 | the markfr ditherings

# September 29, 2008 9:04 PM

Karl Seguin said:

A couple weeks ago we looked at the importance of making tests robust - which can be accomplished by

# October 15, 2008 8:28 PM

Mirrored Blogs said:

A couple weeks ago we looked at the importance of making tests robust - which can be accomplished by

# October 15, 2008 9:24 PM

Community Blogs said:

A couple weeks ago we looked at the importance of making tests robust - which can be accomplished by

# October 15, 2008 11:29 PM

savaş oyunu said:

thanks from turkey

# December 26, 2008 6:59 PM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About karl

I'm a developer living in Ottawa, Ontario. I like to focus on medium to large scale enterprise development, maintainable code, DDD and unit testing. I occasionally speak at conferences, am an editor for DotNetSlackers and contribute to projects here and there. Check out Devlicio.us!

Our Sponsors

Proudly Partnered With