Karl Seguin

Sponsors

The Lounge

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
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.


Posted Fri, Sep 26 2008 9:38 AM by karl

[Advertisement]

Comments

Ryan Gray wrote re: Simplicity is key to successful unit testing
on Fri, Sep 26 2008 10:35 AM

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?

Ryan Gray wrote re: Simplicity is key to successful unit testing
on Fri, Sep 26 2008 10:36 AM

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?

Kevin Gao wrote re: Simplicity is key to successful unit testing
on Fri, Sep 26 2008 11:40 AM

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

karl wrote re: Simplicity is key to successful unit testing
on Fri, Sep 26 2008 12:43 PM

@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;

       }

Jeremy D. Miller wrote re: Simplicity is key to successful unit testing
on Fri, Sep 26 2008 1:24 PM

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

Dew Drop - September 27, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - September 27, 2008 | Alvin Ashcraft's Morning Dew
on Sat, Sep 27 2008 7:57 AM

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

Arjan`s World » LINKBLOG for September 27, 2008 wrote Arjan`s World &raquo; LINKBLOG for September 27, 2008
on Sat, Sep 27 2008 5:07 PM

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

2008 September 29 - Links for today « My (almost) Daily Links wrote 2008 September 29 - Links for today &laquo; My (almost) Daily Links
on Mon, Sep 29 2008 2:52 AM

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

Reflective Perspective - Chris Alcock » The Morning Brew # 189 wrote Reflective Perspective - Chris Alcock &raquo; The Morning Brew # 189
on Mon, Sep 29 2008 3:11 AM

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

links for 2008-09-29 | the markfr ditherings wrote links for 2008-09-29 | the markfr ditherings
on Mon, Sep 29 2008 9:04 PM

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

Karl Seguin wrote Simplicity is key to successful unit testing - Part 2
on Wed, Oct 15 2008 8:28 PM

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

Mirrored Blogs wrote Simplicity is key to successful unit testing - Part 2
on Wed, Oct 15 2008 9:24 PM

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

Community Blogs wrote Simplicity is key to successful unit testing - Part 2
on Wed, Oct 15 2008 11:29 PM

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

savaş oyunu wrote re: Simplicity is key to successful unit testing
on Fri, Dec 26 2008 6:59 PM

thanks from turkey

Invalid Argument » Simplicity is key to successful unit testing wrote Invalid Argument &raquo; Simplicity is key to successful unit testing
on Sun, Jan 18 2009 8:41 AM

Pingback from  Invalid Argument &raquo; Simplicity is key to successful unit testing