Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 20, 2022 07:47 am GMT

Reduce your tests cognitive complexity with AutoFixture

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.cspublic 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.cspublic 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.cspublic 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.cspublic 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.cspublic 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.cspublic 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.cspublic 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.


Original Link: https://dev.to/sfeircode/reduce-your-tests-cognitive-complexity-with-autofixture-2dm4

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To