From qa-unit-tests-net
Reference for FluentAssertions - the canonical .NET fluent-assertion library pairable with xUnit / NUnit / MSTest; provides `.Should()` extension API (`.Should().Be()`, `.Should().BeOfType<T>()`, `.Should().Throw<T>()`, `.Should().BeEquivalentTo()` for deep equality, `.Should().Satisfy()` for predicates, `.Should().BeApproximately()` for floats); rich failure messages with object structure visualization. Body MUST include the 2024 license change note: v8+ commercial license required for new use; v7 is the last fully OSS version.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-unit-tests-net:fluentassertionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [fluentassertions.com][fa]:
Per fluentassertions.com:
FluentAssertions is the de facto fluent-assertion library for .NET.
Works with any of xunit-tests,
nunit-tests, mstest-tests.
Important license change note (2024): FluentAssertions changed license to commercial starting v8. v7 (and earlier) remain OSS-licensed. For new commercial use, either pay for v8+ or pin to v7. For OSS projects, v7 may be sufficient indefinitely.
This skill is a reference - defines the matcher catalog; doesn't run tests. Pair with one of the test frameworks.
Assert.X methods.BeEquivalentTo.# Pin to v7 for OSS-license safety
dotnet add package FluentAssertions --version 7.0.0
Or current (commercial license):
dotnet add package FluentAssertions
using FluentAssertions;
result.Should().Be(42);
list.Should().HaveCount(3);
string.Should().StartWith("Hello");
exception.Should().Be<ArgumentNullException>();
The .Should() extension method provides the fluent entry-point.
Per fluentassertions.com/introduction:
Equality:
value.Should().Be(expected);
value.Should().NotBe(expected);
value.Should().BeNull();
value.Should().NotBeNull();
value.Should().BeSameAs(other); // reference equality
Numeric:
n.Should().BeGreaterThan(0);
n.Should().BeLessThanOrEqualTo(100);
d.Should().BeApproximately(3.14, 0.01);
String:
s.Should().StartWith("prefix");
s.Should().EndWith("suffix");
s.Should().Contain("substring");
s.Should().Match("*wildcard*");
s.Should().MatchRegex(@"\d+");
s.Should().NotBeNullOrEmpty();
Collections:
list.Should().HaveCount(3);
list.Should().Contain("alice");
list.Should().NotContain("eve");
list.Should().ContainInOrder("alice", "bob");
list.Should().BeEquivalentTo(other); // any order
list.Should().AllSatisfy(x => x.Should().BePositive());
Type checks:
result.Should().BeOfType<Success>();
result.Should().BeAssignableTo<IResult>();
Object equivalence (deep):
actual.Should().BeEquivalentTo(expected);
// With options
actual.Should().BeEquivalentTo(expected, opts => opts
.Excluding(x => x.Timestamp)
.ComparingByMembers<MyType>()
.WithStrictOrdering()
);
Exceptions:
Action act = () => DoSomething();
act.Should().Throw<ArgumentException>()
.WithMessage("*invalid*")
.Where(e => e.ParamName == "name");
// Async
Func<Task> asyncAct = async () => await DoSomethingAsync();
await asyncAct.Should().ThrowAsync<HttpRequestException>();
// Should NOT throw
act.Should().NotThrow();
Boolean + null:
flag.Should().BeTrue();
flag.Should().BeFalse();
opt.Should().BeNull();
opt.Should().NotBeNull().And.NotBeEmpty();
Custom predicates:
user.Should().Satisfy(u => u.Email.Contains("@") && u.Age >= 18);
.And chains assertions:
list.Should().HaveCount(3).And.Contain("alice").And.NotContain("eve");
.Which accesses the result for further assertion:
result.Should().BeOfType<Success>()
.Which.Value.Should().Be(42);
FluentAssertions failure output is rich:
Expected list to have 4 items, but found 3:
["alice", "bob", "charlie"]
vs vanilla Assert.AreEqual(4, list.Count):
Expected: 4
But was: 3
The difference matters for debug velocity.
BeEquivalentTo deep equalityMost powerful matcher; structural comparison:
var actual = new User { Id = 1, Name = "Alice", Address = new Address { City = "NYC" } };
var expected = new User { Id = 1, Name = "Alice", Address = new Address { City = "NYC" } };
actual.Should().BeEquivalentTo(expected); // passes (deep equal)
// Even with different types (record vs class):
var dto = new UserDto { Id = 1, Name = "Alice" };
user.Should().BeEquivalentTo(dto, opts => opts
.Excluding(u => u.PasswordHash)); // ignore field
Options control: Excluding, Including, ComparingByMembers,
WithStrictOrdering, WithoutStrictOrdering, IgnoringCyclicReferences.
For migration FROM:
Assert.AreEqual(expected, actual) → actual.Should().Be(expected)Assert.IsTrue(condition) → condition.Should().BeTrue()Assert.IsInstanceOfType(obj, typeof(MyClass)) → obj.Should().BeOfType<MyClass>()Assert.ThrowsException<E>(action) → action.Should().Throw<E>()Migration cost: low (mechanical). Migration benefit: richer failure messages + chainable assertions.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Mix Assert.X and .Should() styles in same suite | Reader confusion | Pick one + lint enforcement |
Long BeEquivalentTo chains without options | Compares fields you don't care about; brittle | Use Excluding to scope (Step 6) |
| Pin v8+ in OSS project without paying | License violation | Pin v7 (Step 1) |
value.Should().Be(true) instead of BeTrue() | Loses semantic clarity | Use BeTrue() (Step 3) |
Skip WithMessage on exception assertions | Pass for wrong exception type | Always specify expected message (Step 3) |
BeEquivalentTo (cyclic refs, polymorphism)
need explicit options..Should() extension can clash with other libraries' extensions
(rare).xunit-tests,
nunit-tests,
mstest-tests - sister tools (test runners)test-code-conventionsProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.
npx claudepluginhub testland/qa --plugin qa-unit-tests-net