AutoFixture Freeze by Mark Seemann
One of the important points of AutoFixture is to hide away all the boring details that you don't care about when you are writing a unit test, but that the compiler seems to insist upon. One of these details is how you create a new instance of your SUT.
Every time you create an instance of your SUT using its constructor, you make it more difficult to refactor that constructor. This is particularly true when it comes to Constructor Injection because you often need to define a Test Double in each unit test, but even for primitive types, it's more maintenance-friendly to use a SUT Factory.
AutoFixture is a SUT Factory, so we can use it to create instances of our SUTs. However, how do we correlate constructor parameters with variables in the test when we will not use the constructor directly?
This is where the Freeze method comes in handy, but let's first examine how to do it with the core API methods CreateAnonymous and Register.
Imagine that we want to write a unit test for a Pizza class that takes a name in its constructor and exposes that name as a property. We can write this test like this:
[TestMethod] public void NameIsCorrect() { // Fixture setup var fixture = new Fixture(); var expectedName = fixture.CreateAnonymous("Name"); fixture.Register(expectedName); var sut = fixture.CreateAnonymous<Pizza>(); // Exercise system string result = sut.Name; // Verify outcome Assert.AreEqual(expectedName, result, "Name"); // Teardown }
The important lines are these two:
var expectedName = fixture.CreateAnonymous("Name"); fixture.Register(expectedName);
What's going on here is that we create a new string, and then we subsequently Register this string so that every time the fixture instance is asked to create a string, it will return this particular string. This also means that when we ask AutoFixture to create an instance of Pizza, it will use that string as the constructor parameter.
It turned out that we used this coding idiom so much that we decided to encapsulate it in a convenience method. After some debate we arrived at the name Freeze, because we essentially freeze a single anonymous variable in the fixture, bypassing the default algorithm for creating new instances. Incidentally, this is one of very few methods in AutoFixture that breaks CQS, but although that bugs me a little, the Freeze concept has turned out to be so powerful that I live with it.
Here is the same test rewritten to use the Freeze method:
[TestMethod] public void NameIsCorrect_Freeze() { // Fixture setup var fixture = new Fixture(); var expectedName = fixture.Freeze("Name"); var sut = fixture.CreateAnonymous<Pizza>(); // Exercise system string result = sut.Name; // Verify outcome Assert.AreEqual(expectedName, result, "Name"); // Teardown }
In this example, we only save a single line of code, but apart from that, the test also becomes a little more communicative because it explicitly calls out that this particular string is frozen.
However, this is still a pretty lame example, but while I intend to follow up with a more complex example, I wanted to introduce the concept gently.
For completeness sake, here's the Pizza class:
public class Pizza { private readonly string name; public Pizza(string name) { if (name == null) { throw new ArgumentNullException("name"); } this.name = name; } public string Name { get { return this.name; } } }
As you can see, the test simply verifies that the constructor parameter is echoed by the Name property, and the Freeze method makes this more explicit while we still enjoy the indirection of not invoking the constructor directly.
Comments
I am trying to understand how, in this particular example, AutoFixture makes the set up impervious to changes in the constructor.
Say that for whatever reason the Pizza constructor takes another parameter e.g.
public Pizza(string name, price decimal)
Then surely, we'd have to update the test given. Am I missing something?
Try it out :)
Thanks for the prompt reply. Now, I think I understand what's going. Effectively, I was not appreciating that the purpose of "Freezing" was to have the string parameter "Name" "frozen" so that the assertion could be made against a known value.
But your explanation has clarified the issue. Thanks very much.
WesM, thank you for writing. Perhaps you'll find this article helpful.