CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/dotnet-unit-tests

.NET unit testing (C# / F# / VB.NET) with xUnit.net as the primary framework - `[Fact]` single tests, `[Theory]` + `[InlineData]`/`[ClassData]`/`[MemberData]` parametrization, class and collection fixtures (`IClassFixture` / `ICollectionFixture`), parallel-execution config, `ITestOutputHelper` output, skip/traits filtering, and `dotnet test` CI with trx + coverage. Includes framework choice (xUnit for new projects; match an existing NUnit/MSTest convention detected from csproj PackageReferences; legacy .NET Framework 4.x → NUnit or MSTest) and test-authoring conventions (AAA mapping, argument-order traps, no fabricated methods, no smoke asserts). References cover NUnit (`[TestCase]`, constraint-model `Assert.That`), MSTest (`[TestClass]` / `[DataRow]` / TestContext), and the FluentAssertions `.Should()` catalog including the v8 commercial-license change. Use for any .NET unit-test task: choosing or configuring a framework, writing or parameterizing tests, fixtures, or wiring CI.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

fluentassertions.mdreferences/

FluentAssertions - fluent .NET assertions (reference)

Companion reference for dotnet-unit-tests. Per fluentassertions.com, FluentAssertions is the de facto fluent-assertion library for .NET - it pairs with xUnit, NUnit, or MSTest (auto-detects the framework and throws framework-specific exceptions), so assertion code survives a move between frameworks.

Important license change note: from v8, "commercial use requires a paid license", while v8+ stays "free for open-source projects and non-commercial use"; v7 "will remain fully open-source indefinitely" (per fluentassertions.com/releases). Commercial projects either buy a v8+ license or pin to v7; open-source and non-commercial projects can use v8+ free.

Install

dotnet add package FluentAssertions                    # current v8+ (see license note above)
dotnet add package FluentAssertions --version 7.0.0    # pin v7 for fully-OSS commercial use

Basic syntax

Per fluentassertions.com/introduction - .Should() is the fluent entry point:

using FluentAssertions;

result.Should().Be(42);
list.Should().HaveCount(3);

Matcher catalog

// Equality
value.Should().Be(expected);
value.Should().NotBe(expected);
value.Should().BeNull();
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().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().AllSatisfy(x => x.Should().BePositive());

// Type checks
result.Should().BeOfType<Success>();
result.Should().BeAssignableTo<IResult>();

// Boolean
flag.Should().BeTrue();

// Custom predicates
user.Should().Satisfy(u => u.Email.Contains("@") && u.Age >= 18);

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();

Always specify WithMessage - a type-only assertion passes for the wrong failure of the right type.

Chaining

.And chains assertions; .Which accesses the result for further assertion:

list.Should().HaveCount(3).And.Contain("alice").And.NotContain("eve");

result.Should().BeOfType<Success>()
              .Which.Value.Should().Be(42);

BeEquivalentTo deep equality

Structural comparison - the most powerful matcher (fluentassertions.com/objectgraphs):

actual.Should().BeEquivalentTo(expected);   // deep equal, order-independent

// Across different types (record vs class), excluding fields:
user.Should().BeEquivalentTo(dto, opts => opts
    .Excluding(u => u.PasswordHash));

// With options
actual.Should().BeEquivalentTo(expected, opts => opts
    .Excluding(x => x.Timestamp)
    .ComparingByMembers<MyType>()
    .WithStrictOrdering()
);

Options: Excluding, Including, ComparingByMembers, WithStrictOrdering, WithoutStrictOrdering, IgnoringCyclicReferences.

Migration from Assert.X

  • 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>()

Mechanical, low-cost; the benefit is richer failure messages (object structure shown, e.g. Expected list to have 4 items, but found 3: ["alice", "bob", "charlie"]) plus chainability.

Anti-patterns

Anti-patternWhy it failsFix
Mix Assert.X and .Should() in one suiteReader confusionPick one + lint enforcement
BeEquivalentTo without options on volatile fieldsCompares fields you don't care about; brittleExcluding(...)
Ship v8+ commercially without a paid licenseLicense violationBuy a v8+ license or pin v7
value.Should().Be(true)Loses semantic clarityBeTrue() / BeFalse()

Limitations

  • BeEquivalentTo edge cases (cyclic refs, polymorphism) need explicit options.
  • .Should() can clash with other libraries' extension methods (rare).
  • C#-first; F# usage is less ergonomic.

References

  • fa - FluentAssertions landing
  • fa-intro - introduction guide
  • fluentassertions.com/objectgraphs - BeEquivalentTo deep dive
  • fluentassertions.com/releases - v8 license change
  • github.com/fluentassertions/fluentassertions - repository

SKILL.md

tile.json