CtrlK
BlogDocsLog inGet started
Tessl Logo

testing-guide

Write tests for an FSH feature — xUnit + Shouldly + NSubstitute + AutoFixture, with naming and AAA conventions. Use when adding unit/handler/validator/entity tests. Full rules in .agents/rules/testing.md + integration-testing.md.

71

Quality

86%

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

SKILL.md
Quality
Evals
Security

Testing Guide

Stack: xUnit + Shouldly (.ShouldBe) + NSubstitute (Substitute.For<>) + AutoFixture (new Fixture()). Not Moq, not FluentAssertions. Detailed conventions + integration-test gotchas live in .agents/rules/testing.md and .agents/rules/integration-testing.md.

Conventions

  • Test class: public sealed class {Sut}Tests; SUT field named _sut.
  • Method name: MethodName_Should_ExpectedBehavior[_When_Condition].
  • Arrange-Act-Assert with // Arrange / // Act / // Assert; group with #region (Happy Path / Guards / Edge Cases).
  • Mocks via Substitute.For<IService>(); assert calls with .Received(1).X(arg, Arg.Any<CancellationToken>()).
  • When asserting a forwarded CancellationToken, assert the specific token, not the default (NSubstitute fills optional params with default).

Handler test

public sealed class Create{Entity}CommandHandlerTests
{
    private readonly {X}DbContext _db;            // or Substitute.For<IService>() for service deps
    private readonly Create{Entity}CommandHandler _sut;
    private readonly IFixture _fixture = new Fixture();

    public Create{Entity}CommandHandlerTests()
    {
        _db = /* in-memory or test DbContext */;
        _sut = new Create{Entity}CommandHandler(_db);
    }

    [Fact]
    public async Task Handle_Should_PersistEntity_And_ReturnId()
    {
        // Arrange
        var command = new Create{Entity}Command(_fixture.Create<string>(), 9.99m, "USD");

        // Act
        var id = await _sut.Handle(command, CancellationToken.None);

        // Assert
        id.ShouldNotBe(Guid.Empty);
    }
}

Service-dependency example (NSubstitute):

_userService = Substitute.For<IUserService>();
// Act … then:
await _userService.Received(1).ToggleStatusAsync(true, command.UserId, Arg.Any<CancellationToken>());

Validator test

public sealed class Create{Entity}CommandValidatorTests
{
    private readonly Create{Entity}CommandValidator _sut = new();

    [Theory]
    [InlineData("")]
    public void Validate_Should_Fail_When_NameInvalid(string name)
    {
        var result = _sut.Validate(new Create{Entity}Command(name, 1m, "USD"));
        result.IsValid.ShouldBeFalse();
        result.Errors.ShouldContain(e => e.PropertyName == nameof(Create{Entity}Command.Name));
    }
}

Entity / domain test (no mocks)

[Fact]
public void Create_Should_RaiseCreatedEvent()
{
    var entity = {Entity}.Create("Test", Money.Zero());
    entity.Id.ShouldNotBe(Guid.Empty);
    entity.DomainEvents.ShouldContain(e => e is {Entity}CreatedDomainEvent);
}

Architecture tests (guardrails — keep green)

Architecture.Tests (NetArchTest) enforce: module boundaries (cross-module refs only via .Contracts), tenant-isolation rules, handlers sealed, and every command/paginated-query handler has a validator. Don't weaken these to make a change pass — fix the code.

Integration tests

Integration.Tests runs over real Postgres/Redis/MinIO via Testcontainers — Docker required. Set the Finbuckle tenant context inline, rewire IStorageService post-registration for MinIO, force long-polling for SignalR. All detailed in .agents/rules/integration-testing.md.

Run

dotnet test src/Tests/{X}.Tests
dotnet test src/Tests/Architecture.Tests
dotnet test src/FSH.Starter.slnx --collect "XPlat Code Coverage" --settings coverage.runsettings
Repository
fullstackhero/dotnet-starter-kit
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.