When unit testing your components, you may often be in a situation when you must provide several parameters but only one is relevant.
Ensuring that your test is still readable and not bloated by the setup of those variables may be quite a challenge but hopefully no more with AutoFixture, let's see how!
Setup
If you want to, you can make the same manipulations as I am doing, like a small hands on lab on AutoFixture. If not, you may skip this chapter and head directly to the case study.
The setup here is really minimal, just create a new test project. I'll be using xUnit but NUnit should be fine too.
~$ dotnet new xunit -o AutoFixtureExample
You can now open your new project in an editor of your choice and delete the generated UnitTest1.cs
file.
Case study
For us to understand why AutoFixture might be an asset in your projects, we will work on a simple case study: considering a warehouse and an order for clothes, we want to either confirm or refuse to process the order.
In a new file Warehouse.cs
, let's first add our entities:
// Warehouse.cs
public record Cloth(string Name);
public record Order(Cloth cloth, int UnitsOrdered, double Discount);
public class Warehouse
{
}
Finally, append our simple ordering validation inside the Warhouse
class:
// Warehouse.cs
public bool IsValid(Order order)
{
if (order.UnitsOrdered < 1) return false;
// Some more checks on the stocks
return true;
}
Now that we have our logic, we can test it in a new WarehouseTest.cs
file:
// WarehouseTest.cs
public class WarehouseTest
{
[Fact]
public void OrderingWithAnInvalidUnitsCount()
{
var order = new Order(new Cloth("sweat"), -1, 0);
var result = new Warehouse().IsValid(order);
Assert.False(result);
}
}
You may now ensure that our test is passing by running the tests.
Some problem
Our test may pass but it may not be as well written as we would like.
Readability and intent
Let's tackle the first issue here which might be readability.
In our example, the test is fairly simple but a newcomer on the project might not know what the -1
really means, and maybe not that the kind of cloth we are creating here is irrelevant.
We may want to clarify it by naming our variables:
// WarhouseTest.cs
public class WarehouseTest
{
[Fact]
public void OrderingWithAnInvalidUnitsCount()
{
- var order = new Order(new Cloth("sweat"), -1, 0);
+ var invalidUnitsCount = -1;
+ var cloth = new Cloth("This does not matter for the test");
+ var irrelevantDiscount = 0;
+ var order = new Order(cloth, invalidUnitsCount, irrelevantDiscount);
var result = new Warehouse().IsValid(order);
Assert.False(result);
}
}
Variables that are purposely created to indicate that they does not matter are also sometimes referred as anonymous variables in AutoFixture.
It's now a bit more verbose but the intent of the test is clearer and might help someone new to grasp what the parameters are for.
Notice that strings can hold their intents (ex:
"My value does not matter"
) but other types may not such as int, double, etc. That's why we had to name our variable holding the discount with this explicit name.
Surviving refactoring
Another issue that you may face is the classes used in your tests evolving.
Let's say that our Cloth
class now also contains its marketing date, we will have to update our test in consequence:
// Warehouse.cs
- public record Cloth(string Name);
+ public record Cloth(string Name, DateTime MarketingDate);
// WarehouseTest.cs
public class WarehouseTest
{
[Fact]
public void OrderingWithAnInvalidUnitsCount()
{
var invalidUnitsCount = -1;
- var cloth = new Cloth("This does not matter for the test");
+ var cloth = new Cloth("This does not matter for the test", DateTime.Now);
var irrelevantDiscount = 0;
var order = new Order(cloth, invalidUnitsCount, irrelevantDiscount);
var result = new Warehouse().IsValid(order);
Assert.False(result);
}
}
Having this change already impacted our test even with our example that is minimal. If we had more tests or objects using Cloth
we would have a lot more refactoring to do.
You may also notice that we are passing
DateTime.Now
here, which is yet not very readable regarding its intent.
Introducing AutoFixture
Our test is slowly getting more and more bloated with those initialization and may be even more if either of our classes evolve.
Hopefully, using AutoFixture, we can greatly simplify it.
AutoFixture is a NuGet that can generate variables that can be seen as explicitly not significant.
Having a glance at their README, it appears that it is exactly what we would need:
AutoFixture is designed to make Test-Driven Development more productive and unit tests more refactoring-safe. It does so by removing the need for hand-coding anonymous variables as part of a test's Fixture Setup phase.
Let's add AutoFixture and see what's changing !
~/AutoFixtureExample$ dotnet add package AutoFixture
// WarehouseTest.cs
public class WarehouseTest
{
private static readonly IFixture Fixture = new Fixture();
[Fact]
public void OrderingWithAnInvalidUnitsCount()
{
var invalidUnitsCount = -1;
var cloth = Fixture.Create<Cloth>();
var discount = Fixture.Create<double>();
var order = new Order(cloth, invalidUnitsCount, discount);
var result = new Warehouse().IsValid(order);
Assert.False(result);
}
}
That's a little bit better since now we do not have to specify which value is relevant and which one is not. However, the test is still pretty long and we can take advantage of AutoFixture's builder to create our order in an even more straightforward way:
// WarehouseTest.cs
public class WarehouseTest
{
private static readonly IFixture Fixture = new Fixture();
[Fact]
public void OrderingWithAnInvalidUnitsCount()
{
- var invalidUnitsCount = -1;
- var cloth = Fixture.Create<Cloth>();
- var discount = Fixture.Create<double>();
- var order = new Order(cloth, invalidUnitsCount, discount);
+ var order = Fixture.Build<Order>()
+ .With(order => order.UnitsOrdered, -1)
+ .Create();
var result = new Warehouse().IsValid(order);
Assert.False(result);
}
}
We now have a test where only the variable that matters is explicitly set and that does not need to be modified if any class changes.
Take aways
Using AutoFixture, we have greatly improved our test's readability and clarify its intents while also ensuring that it will not break whenever a class's definition changes.
Of course there is much more to learn about this library, such as how to customize the objects generations, creating sequences and more and for that you can refer to their GitHub and the associated cheat sheet that can be a good starting point for using AutoFixture.
Top comments (0)