Replace FluentAssertions with AI

Replace FluentAssertions with AI

Replace FluentAssertions with AI

New year - new .NET drama. You would have thought that positive lessons had been learned from the last Moq drama; but we were proven wrong.

What happened?

The owners of FluentAssertions have apparently decided to switch from a free open source license Apache 2.0 to a commercial license at short notice, without transparency or communication. And the whole thing has already been published with version 8. Earlier releases remain unaffected, as licenses, especially Apache 2.0, cannot be withdrawn across the board. The fact that all pre-versions of FluentAssertions 8 have continued to be released under the Apache 2.0 license indicates that this was done in the short term - for whatever reason, most likely financial interest.

Even though the creators most likely have a very high share of the source code and ideas, FluentAssertions has been a popular project with contributors, both in code and ideas. The switch to a commercial license is a slap in the face for all those who have contributed to the project - especially since the financial profit will most likely remain solely with the owners, although many others have contributed to the project - even with the awareness that this is an Apache 2.0 license. Whether the switch to a commercial license is legal at all, hopefully professionals will take care of that (at least this is an active discussion in the Fluent Assertion License PR).

Yes, we need a solution for sustainable open source projects - but not like this. This is an undignified treatment of everyone who has worked on this project and an immense loss of trust in the entire ecosystem. That is not sustainable open-source software.

What now?

As in the case of Moq, confidence in the project is now shaken. The commercial license is very vague, it is incredibly expensive (130$ for a syntax-sugar library!). The good thing is: FluentAssertions is functionally not an important library. The main purpose of this library is to make the assertions in Unit Test look pretty.

FluentAssertions simply turns Assert.IsTrue(myVar) into myVar.Should().BeTrue(). This is what probably over 99% of users appreciate and use in FluentAssertions.

Sticky version

The simplest variant is not to use version 8. To do this, we set the latest Apache 2.0 version in our csproj or Directory.Packages.props sticky:

<PackageReference Include="PackageName" Version="[7.0.0]" />

Simple Asserts

To get rid of FluentAssertions completely, the easiest way is to remove it and replace it with simple asserts. In most cases, this is very easy and quick to do.

GitHub Copilot and ChatGPT with the GPO 4o model provide excellent results for converting FluentAssertions assertions into simple asserts in just a few seconds.

replace FluentAssertions code with simple asserts

Migrate to Shoudly

FluentAssertions is certainly the most comprehensive assertion library - but who really uses all the features? As I said, well over 99% will limit themselves to the syntax sugars - and for that there are alternatives like Shoudly.

Copilot is able to convert most FluentAssertions assertions into Shouldly. So it only costs us a few seconds per file to replace FluentAssertions.

myVar.Should().BeTrue() becomes myVar.ShouldBe(true).

A custom implementation

The Fluent API of FluentAssertions is extensive, but not complex. Most of the features are not used, so we can generate a Basic API with Copilot, which costs 20 seconds.

create a fluent testing class api for the most important stuff so I can verify my tests with like "Hello".Assert().IsNotNull()

public static class FluentTestExtensions
{
    public static FluentTest<T> Assert<T>(this T value)
    {
        return new FluentTest<T>(value);
    }

    public class FluentTest<T>
    {
        private readonly T _value;

        public FluentTest(T value)
        {
            _value = value;
        }

        public FluentTest<T> IsNotNull()
        {
            Assert.IsNotNull(_value, "Expected value to not be null.");
            return this;
        }

        public FluentTest<T> IsNull()
        {
            Assert.IsNull(_value, "Expected value to be null.");
            return this;
        }

        public FluentTest<T> IsEqualTo(T expected)
        {
            Assert.AreEqual(expected, _value, $"Expected value to be {expected}, but was {_value}.");
            return this;
        }

        public FluentTest<T> IsNotEqualTo(T unexpected)
        {
            Assert.AreNotEqual(unexpected, _value, $"Expected value to not be {unexpected}, but it was.");
            return this;
        }

        public FluentTest<T> IsOfType<U>()
        {
            Assert.IsInstanceOfType(_value, typeof(U), $"Expected value to be of type {typeof(U).Name}, but was {(_value == null ? "null" : _value.GetType().Name)}.");
            return this;
        }

        public FluentTest<T> IsNotOfType<U>()
        {
            Assert.IsNotInstanceOfType(_value, typeof(U), $"Expected value to not be of type {typeof(U).Name}, but it was.");
            return this;
        }

        public FluentTest<T> Satisfies(Func<T, bool> predicate, string message = "Value did not satisfy the condition.")
        {
            Assert.IsTrue(predicate(_value), message);
            return this;
        }

        public FluentTest<T> Fails(Func<T, bool> predicate, string message = "Value unexpectedly satisfied the condition.")
        {
            Assert.IsFalse(predicate(_value), message);
            return this;
        }
    }
}

// Example usage:
public static class Program
{
    public static void Main()
    {
        "Hello"
            .Assert()
            .IsNotNull()
            .IsEqualTo("Hello")
            .IsNotEqualTo("World")
            .IsOfType<string>()
            .Satisfies(s => s.Length > 3, "String length should be greater than 3.");

        42
            .Assert()
            .IsNotNull()
            .IsEqualTo(42)
            .Satisfies(x => x > 0, "Number should be positive.")
            .IsOfType<int>()
            .Fails(x => x < 0, "Number should not be negative.");

        Console.WriteLine("All assertions passed.");
    }
}

Boom, I recreated the most important features of FluentAssertions in 20 seconds - without mentioning the name.

Conclusion

I really liked FluentAssertions. It's a great, intuitive project - and certainly a life's work that has made a name for itself in the community. But one should not overestimate the value of this framework. FluentAssertions is now said to cost as much per person per year as a new Jetbrains C# IDE Rider - really? I want to smoke that stuff too.

FluentAssertions is not an irreplaceable product. It's a human relief - but in times of AI support like Copilot, do I really still need it? I do not think so. You should look at the value realistically.

It will always remain a mystery to me how one can destroy a life's work like FluentAssertions in a single rash or ill-considered action.

Bye FluentAssertions! Was a great time.