The FluentAssertions rug pull
In the beginning of this year, the owner of the .NET library FluentAssertions suddenly released a new version (8) of the library with a new license. Previous versions of the library were licensed under Apache License 2.0. But now the license was replaced by a proprietary one, requiring you to pay a yearly fee per developer. There was a heated discussion on GitHub, with many disappointed people.
On one hand, I understand that with open source software you cannot demand anything. We got free versions for many years, and you can freely continue to use version 7 of FluentAssertions or fork it and continue the development yourself. You cannot demand the original developer, Dennis Doomen, to continue to work for free to support it. I get that he wants to earn some money too. The library has been maintained and provided for free since about 2010.
On the other hand, the library got popular because it was free. People added the NuGet package to their .NET solutions under the premise that it's a free library. If it had been associated with a fee from the beginning, people probably would have made different choices, and perhaps have been more likely to go with another comparable library.
To be frank, I don't value what FluentAssertions provides especially highly. Somebody else in a previous team added it and thought it was nice. Sure, I didn't mind and I continued to use it. It's nice, but I could live without it. If it had been released from the beginning under a non-free license, or if I had known there would eventually be a license change, I wouldn't have used it.
The sudden license change felt like a rug pull to me. It's not the first time a project has gotten popular from being free, attracts contributors from being open source, and then changes license to start to capitalize on its user base. It would have been more morally acceptable if the project's creator was upfront with the project being free and open source only temporarily. Then people would better be able to decide if it's something they want to either use or contribute to.
In this case with FluentAssertions, I simply replaced the library with Shouldly. It has a very similar syntax, so it's very easy to switch to it. In one project where I made this switch, I was able to migrate about 95% of the calls to FluentAssertions functions by doing search and replace in the code base of the following things:
Should().BeTrue
->ShouldBeTrue
Should().HaveCount
->Count.ShouldBe
Should().Equal
->ShouldBe
Should().BeLessOrEqualTo
->ShouldBeLessThanOrEqualTo
Should().NotBeNull
->ShouldNotBeNull
Should().NotThrow
->ShouldNotThrow
Should().OnlyContain
->ShouldAllBe
Should().NotContain
->ShouldNotContain
Should().StartWith
->ShouldStartWith
Should().Be
->ShouldBe
The remaining 5% of the FluentAssertions calls that couldn't be replaced as easily were, for instance, assertions of thrown exceptions. But overall, moving one .NET solution from FluentAssertions to Shouldly probably took me less than an hour.
Here's an example of a unit test class defined with MSTest that uses FluentAssertions:
[TestClass]
[TestSubject(typeof(ConcurrentHashSet<int>))]
public class ConcurrentHashSetTests
{
readonly ConcurrentHashSet<int> _set = [];
[TestMethod]
public void Add_AddsElementToSet()
{
_set.Add(1);
_set.Contains(1).Should().BeTrue();
}
[TestMethod]
public void Add_ReturnsFalseIfElementAlreadyExists()
{
_set.Add(1);
_set.Add(1).Should().BeFalse();
}
[TestMethod]
public void GetEnumerator_ReturnsAllElementsInSet()
{
_set.Add(1);
_set.Add(2);
_set.Add(3);
var elements = _set.ToList();
elements.Should().Contain(1);
elements.Should().Contain(2);
elements.Should().Contain(3);
}
}
And here's the same class but with the assertions replaced with Shouldly:
[TestClass]
[TestSubject(typeof(ConcurrentHashSet<int>))]
public class ConcurrentHashSetTests
{
readonly ConcurrentHashSet<int> _set = [];
[TestMethod]
public void Add_AddsElementToSet()
{
_set.Add(1);
_set.Contains(1).ShouldBeTrue();
}
[TestMethod]
public void Add_ReturnsFalseIfElementAlreadyExists()
{
_set.Add(1);
_set.Add(1).ShouldBeFalse();
}
[TestMethod]
public void GetEnumerator_ReturnsAllElementsInSet()
{
_set.Add(1);
_set.Add(2);
_set.Add(3);
var elements = _set.ToList();
elements.ShouldContain(1);
elements.ShouldContain(2);
elements.ShouldContain(3);
}
}
As you can see, almost the same. So if you are still using FluentAssertions in your .NET solution(s), consider switching to Shouldly.