Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Dotnet Testing

ASecurity

xUnit, Moq/NSubstitute, FluentAssertions, and coverlet patterns for any .NET project. Covers test structure ([Fact]/[Theory]/[InlineData]), mocking discipline, fluent assertions, integration test setup with WebApplicationFactory, and coverage measurement. Stack-agnostic — referenced by every .NET plugin in the marketplace. Use this skill to: - Write clear, maintainable unit tests with xUnit [Fact] and [Theory]. - Mock dependencies with Moq or NSubstitute without overusing mocks. - Write expr...

35 stars
0 votes
0 copies
0 views
Added 9/22/2026
testingc#bashsqlexpresstestingdatabase

Works with

cli

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add AratKruglik/claude-sdlc --skill dotnet-testing --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dotnet Testing?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Dotnet Testing
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aratkruglik-dotnet-testing/badge)](https://www.skillsdirectory.com/skills/aratkruglik-dotnet-testing)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: dotnet-testing
description: |
  xUnit, Moq/NSubstitute, FluentAssertions, and coverlet patterns for any .NET project. Covers test structure ([Fact]/[Theory]/[InlineData]), mocking discipline, fluent assertions, integration test setup with WebApplicationFactory, and coverage measurement. Stack-agnostic — referenced by every .NET plugin in the marketplace.

  Use this skill to:
  - Write clear, maintainable unit tests with xUnit [Fact] and [Theory].
  - Mock dependencies with Moq or NSubstitute without overusing mocks.
  - Write expressive assertions with FluentAssertions.
  - Measure coverage with coverlet and enforce a minimum threshold.

  Do NOT use this skill for:
  - ASP.NET Core-specific integration tests (WebApplicationFactory, HttpClient — those are in aspnet-core-plugin:aspnet-conventions).
  - EF Core in-memory or SQL Server LocalDB test patterns (aspnet-core-plugin:efcore-patterns).
  - C# language idioms — see csharp-foundation:csharp-conventions.
user-invocable: false
paths: ["**/*Tests/**", "**/*Test*.cs"]
---

# .NET Testing Patterns (stack-agnostic)

## Test framework: xUnit

xUnit is the primary test framework for .NET. Use NUnit or MSTest only when the project already uses them — do not introduce xUnit into a project that uses another framework.

### NuGet packages for a test project

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <IsPackable>false</IsPackable>
    <IsTestProject>true</IsTestProject>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="xunit" Version="2.9.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
    <PackageReference Include="Moq" Version="4.20.72" />
    <PackageReference Include="FluentAssertions" Version="6.12.1" />
    <PackageReference Include="coverlet.collector" Version="6.0.2">
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
</Project>
```

## xUnit fundamentals

```csharp
using FluentAssertions;
using Moq;
using Xunit;

public class UserServiceTests
{
    private readonly Mock<IUserRepository> _repoMock;
    private readonly UserService _sut;

    public UserServiceTests()
    {
        _repoMock = new Mock<IUserRepository>(MockBehavior.Strict);
        _sut = new UserService(_repoMock.Object);
    }

    [Fact]
    public async Task RegisterAsync_WithValidData_ReturnsActiveUser()
    {
        // Arrange
        var command = new RegisterUserCommand("alice@example.com", "Secret1!");
        _repoMock.Setup(r => r.ExistsByEmailAsync("alice@example.com", default))
                 .ReturnsAsync(false);
        _repoMock.Setup(r => r.SaveAsync(It.IsAny<User>(), default))
                 .ReturnsAsync((User u, CancellationToken _) => u);

        // Act
        var user = await _sut.RegisterAsync(command);

        // Assert
        user.Email.Should().Be("alice@example.com");
        user.IsActive.Should().BeTrue();
        _repoMock.VerifyAll();
    }

    [Fact]
    public async Task RegisterAsync_WithDuplicateEmail_ThrowsDomainException()
    {
        _repoMock.Setup(r => r.ExistsByEmailAsync(It.IsAny<string>(), default))
                 .ReturnsAsync(true);

        await _sut.Invoking(s => s.RegisterAsync(new RegisterUserCommand("dup@example.com", "pass")))
                  .Should().ThrowAsync<DomainException>()
                  .WithMessage("*already registered*");
    }
}
```

**Test method naming:** `MethodName_Condition_ExpectedOutcome` — readable without additional comments.

**xUnit constructor vs `[Theory]` setup:** Use the constructor for shared setup of the system under test; use `[ClassFixture<T>]` for expensive shared resources (DB connections, servers) that are reused across tests in the class.

## Parameterised tests — [Theory]

```csharp
[Theory]
[InlineData("")]
[InlineData("  ")]
[InlineData(null)]
public void IsValidEmail_BlankInput_ReturnsFalse(string? input)
{
    EmailValidator.IsValid(input).Should().BeFalse();
}

[Theory]
[InlineData("alice@example.com", true)]
[InlineData("not-an-email", false)]
[InlineData("@nodomain", false)]
public void IsValidEmail_VariousInputs_MatchesExpected(string email, bool expected)
{
    EmailValidator.IsValid(email).Should().Be(expected);
}

// MemberData for complex objects
[Theory]
[MemberData(nameof(InvalidCommands))]
public async Task RegisterAsync_InvalidCommand_ThrowsValidationException(RegisterUserCommand command)
{
    await _sut.Invoking(s => s.RegisterAsync(command))
              .Should().ThrowAsync<ValidationException>();
}

public static IEnumerable<object[]> InvalidCommands =>
[
    [new RegisterUserCommand(null!, "pass")],
    [new RegisterUserCommand("", "pass")],
    [new RegisterUserCommand("user@example.com", "")],
];
```

## FluentAssertions — expressive assertions

Prefer FluentAssertions over xUnit's `Assert.*` — it gives richer failure messages and fluent chaining.

```csharp
// Collections
result.Items.Should().HaveCount(3)
    .And.ContainSingle(i => i.Name == "Alice");

// Strings
response.ContentType.Should().StartWith("application/json");

// Exceptions (sync and async)
Action act = () => service.Compute(-1);
act.Should().Throw<ArgumentOutOfRangeException>()
   .WithParameterName("value");

await service.Awaiting(s => s.GetAsync(-1))
    .Should().ThrowAsync<KeyNotFoundException>();

// Numeric / date
price.Should().BeGreaterThan(0).And.BeLessThan(1000);
createdAt.Should().BeCloseTo(DateTimeOffset.UtcNow, precision: TimeSpan.FromSeconds(5));

// Equivalence (structural equality, ignores property order)
result.Should().BeEquivalentTo(expected, opt => opt.Excluding(x => x.Id));
```

**Never use `Assert.True(a.Equals(b))`** — the failure message shows only "expected true". Use `a.Should().Be(b)`.

## Moq — mocking discipline

```csharp
// Constructor injection — preferred (no reflection, no [Inject])
var repo = new Mock<IUserRepository>(MockBehavior.Strict);
var sut = new UserService(repo.Object);

// Stubbing — be specific with arguments
repo.Setup(r => r.FindAsync(42, default)).ReturnsAsync(testUser);

// Argument matchers when exact value doesn't matter
repo.Setup(r => r.ExistsByEmailAsync(It.IsAny<string>(), default))
    .ReturnsAsync(false);

// Capture arguments for detailed assertions
repo.Setup(r => r.SaveAsync(It.IsAny<User>(), default))
    .Callback<User, CancellationToken>((u, _) => savedUser = u)
    .ReturnsAsync((User u, CancellationToken _) => u);

// Verification
repo.Verify(r => r.SaveAsync(It.IsAny<User>(), default), Times.Once);
repo.Verify(r => r.DeleteAsync(It.IsAny<int>(), default), Times.Never);
repo.VerifyAll();   // MockBehavior.Strict enforces no unexpected calls
```

**Use `MockBehavior.Strict`** for new tests — it forces you to set up all calls explicitly, making unexpected dependencies visible.

**Do not mock value objects, records, or simple DTOs.** Only mock boundaries: repositories, HTTP clients, external services, the clock (`IDateTimeProvider`).

## NSubstitute — alternative to Moq

Some projects prefer NSubstitute for its concise syntax:

```csharp
var repo = Substitute.For<IUserRepository>();
repo.FindAsync(42, default).Returns(testUser);
repo.ExistsByEmailAsync(Arg.Any<string>(), default).Returns(false);

// Verification
await repo.Received(1).SaveAsync(Arg.Any<User>(), default);
await repo.DidNotReceive().DeleteAsync(Arg.Any<int>(), default);
```

Match the mocking framework that is already used in the project. Do not mix Moq and NSubstitute in the same test project.

## Coverage — coverlet + threshold

Measure coverage when running tests:

```bash
# Run tests with coverage
dotnet test --collect:"XPlat Code Coverage"

# Generate HTML report (requires reportgenerator global tool)
dotnet tool install --global dotnet-reportgenerator-globaltool
reportgenerator -reports:**/coverage.cobertura.xml -targetdir:coverage -reporttypes:Html

# Open report
open coverage/index.html     # macOS
start coverage/index.html    # Windows
```

Enforce a minimum coverage threshold in CI:

```xml
<!-- .csproj — dotnet test fails if line coverage < 80% -->
<PropertyGroup>
  <CollectCoverage>true</CollectCoverage>
  <CoverletOutputFormat>cobertura</CoverletOutputFormat>
  <Threshold>80</Threshold>
  <ThresholdType>line</ThresholdType>
</PropertyGroup>
```

**Target ≥ 80% line coverage on business logic** (services, domain objects, validators). Exclude framework glue code (Program.cs, Startup, configuration classes, migrations) using `[ExcludeFromCodeCoverage]` or coverlet filters.

## Test organisation conventions

```
MyApp.sln
├── src/
│   └── MyApp/
│       ├── Users/
│       │   ├── User.cs
│       │   ├── IUserRepository.cs
│       │   └── UserService.cs
│       └── ...
└── tests/
    ├── MyApp.UnitTests/            # Unit tests — no external dependencies
    │   └── Users/
    │       └── UserServiceTests.cs
    └── MyApp.IntegrationTests/     # Integration tests — real DB, HTTP
        └── Users/
            └── UserEndpointsTests.cs
```

**Naming:** Unit test classes: `{Subject}Tests`. Integration test classes: `{Subject}IntegrationTests` or `{Subject}Tests` in the `.IntegrationTests` project.

Mirror the main project's namespace structure: `MyApp.Users.UserServiceTests` tests `MyApp.Users.UserService`.

## IClassFixture — reuse expensive resources

```csharp
// Shared fixture — created once per test class, disposed after all tests
public class DatabaseFixture : IDisposable
{
    public AppDbContext Db { get; }

    public DatabaseFixture()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        Db = new AppDbContext(options);
    }

    public void Dispose() => Db.Dispose();
}

public class UserRepositoryTests : IClassFixture<DatabaseFixture>
{
    private readonly AppDbContext _db;

    public UserRepositoryTests(DatabaseFixture fixture) => _db = fixture.Db;

    [Fact]
    public async Task SaveAndFind_RoundTrip()
    {
        var repo = new UserRepository(_db);
        var user = new User { Email = "alice@example.com" };

        await repo.SaveAsync(user, default);
        var found = await repo.FindAsync(user.Id, default);

        found.Should().NotBeNull();
        found!.Email.Should().Be("alice@example.com");
    }
}
```

For ASP.NET Core HTTP integration tests using `WebApplicationFactory<TProgram>`, see `aspnet-core-plugin:aspnet-conventions`.

Attribution

AratKruglikAratKruglik
View sourceMore from AratKruglik →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Screen Reader Testing

Practical guide to testing web applications with screen readers for comprehensive accessibility validation.

397921 votes

Tdd Workflow

在编写新功能、修复错误或重构代码时使用此技能。强制执行测试驱动开发,包含单元测试、集成测试和端到端测试,覆盖率超过80%。

2456590 votes

Python Testing

使用pytest、TDD方法、夹具、模拟、参数化和覆盖率要求的Python测试策略。

2456590 votes

Springboot Tdd

使用JUnit 5、Mockito、MockMvc、Testcontainers和JaCoCo进行Spring Boot的测试驱动开发。适用于添加功能、修复错误或重构时。

2456590 votes

Golang Testing

Go测试模式包括表格驱动测试、子测试、基准测试、模糊测试和测试覆盖率。遵循TDD方法论,采用地道的Go实践。

2456590 votes
View all in testing →