本文へ移動
cccskills
無料GitHub で公開

dotnet-testing

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.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md10.5 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

.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

<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

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]

[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.

// 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

// 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:

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:

# 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:

<!-- .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

// 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.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する