As a response to my description of how AutoFixture creates objects, Klaus asked:

“[What] if the constructor of ComplexChild imposes some kind of restriction on its parameter? If, for example, instead of the "name" parameter, it would take a "phoneNumber" parameter (as a string), and do some format checking?”

Now that we have covered some of the basic features of AutoFixture, it's time to properly answer this excellent question.

For simplicity's sake, let's assume that the phone number in question is a Danish phone number: This is pretty good for example code, since a Danish phone number is essentially just an 8-digit number. It can have white space and an optional country code (+45), but strip that away, and it's just an 8-digit number. However, there are exceptions, since the emergency number is 112 (equivalent to the American 911), and other 3-digit special numbers exist as well.

With that in mind, let's look at a simple Contact class that contains a contact's name and Danish phone number. The constructor might look like this:

public Contact(string name, string phoneNumber) {     this.Name = name;     this.PhoneNumber =         Contact.ParsePhoneNumber(phoneNumber); }

The static ParsePhoneNumber method strips away white space and optional country code and parses the normalized string to a number. This fits the scenario laid out in Klaus' question.

So what happens when we ask AutoFixture to create an instance of Contact? It will Reflect over Contact's constructor and create two new anonymous string instances - one for name, and one for phoneNumber. As previously described, each string will be created as a Guid prepended with a named hint - in this case the argument name. Thus, the phoneNumber argument will get a value like "phoneNumberfa432351-1563-4769-842c-7588af32a056", which will cause the ParsePhoneNumber method to throw an exception.

How do we deal with that?

The most obvious fix is to modify AutoFixture's algorithm for generating strings. Here an initial attempt:

fixture.Register<string>(() => "112");

This will simply cause all generated strings to be "112", including the Contact instance's Name property. In unit testing, this may not be a problem in itself, since, from an API perspective, the name could in principle be any string.

However, if the Contact class also had an Email property that was parsed and verified from a string argument, we'd be in trouble, since "112" is not a valid email address.

We can't easily modify the string generation algorithm to fit the requirements for both a Danish telephone number and an email address.

Should we then conclude that AutoFixture isn't really useful after all?

On the contrary, this is a hint to us that the Contact class' API could be better. If an automated tool can't figure out how to generate correct input, how can we expect other developers to do it?

Although humans can make leaps of intuition, an API should still go to great lengths to protect its users from making mistakes. Asking for an unbounded string and then expecting it to be in a particular format may not always be the best option available.

In our particular case, the Value Object pattern offers a better alternative. Our first version of the DanishPhoneNumber class simply takes an integer as a constructor argument:

public DanishPhoneNumber(int number) {     this.number = number; }

If we still need to parse strings (e.g. from user input), we could add a static Parse, or even a TryParse, method and test that method in isolation without involving the Contact class.

This neatly solves our original issue with AutoFixture, since it will now create a new instance of DanishPhoneNumber as part of the creation process when we ask for an anonymous Contact instance.

The only remaining issue is that by default, the number fed into the DanishPhoneNumber instance is likely to be considerably less than 112 - actually, if no other Int32 instances are created, it will be 1.

This will be a problem if we modify the DanishPhoneNumber constructor to look like this:

public DanishPhoneNumber(int number) {     if ((number < 112) ||         (number > 99999999))     {         throw new ArgumentOutOfRangeException("number");     }     this.number = number; }

Unless a unit test has already caused AutFixture to previously create 111 other integers (highly unlikely), CreateAnonymous<Contact> is going to throw an exception.

This is easy to fix. Once again, the most obvious fix is to modify the creation algorithm for integers.

fixture.Register<int>(() => 12345678);

However, this will cause that particular instance of Fixture to return 12345678 every time you ask it to create an anonymous integer. Depending on the scenario, this may or may not be a problem.

A more targeted solution is to specifically address the algorithm for generating DanishPhoneNumber instances:

fixture.Register<int, DanishPhoneNumber>(i =>     new DanishPhoneNumber(i + 112));

Here, I've even used the Register overload that automatically provides an anonymous integer to feed into the DanishPhoneNumber constructor, so all I have to do is ensure that the number falls into the proper range. Adding 112 (the minimum) neatly does the trick.

If you don't like the hard-coded value of 112 in the test, you can use that to further drive the design. In this case, we can add a MinValue to DanishPhoneNumber:

fixture.Register<int, DanishPhoneNumber>(i =>     new DanishPhoneNumber(i +         DanishPhoneNumber.MinValue));

Obvously, MinValue will also be used in DanishPhoneNumber's constructor to define the lower limit of the Guard Clause.

In my opinion, a good API should guide the user and make it difficult to make mistakes. In many ways, you can view AutoFixture as an exceptionally dim user of your API. This is the reason I really enjoyed receiving Klaus' original question: Like other TDD practices, AutoFixture drives better design.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Friday, 01 May 2009 03:56:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!