dotnet-xunit
Write, run, or repair .NET tests that use xUnit. Use when a repo uses `xunit`, `xunit.v3`, `[Fact]`, `[Theory]`, or `xunit.runner.visualstudio`, and you need the right CLI, package, and runner guidance for xUnit on VSTest or Microsoft.Testing.Platform.
Install
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-xunit
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
xUnit.net
Trigger On
- the repo uses xUnit v2 or xUnit v3
- you need to add, run, debug, or repair xUnit tests
- the team is unsure whether a project is using VSTest or Microsoft.Testing.Platform
Value
- produce a concrete project delta: code, docs, config, tests, CI, or review artifact
- reduce ambiguity through explicit planning, verification, and final validation skills
- leave reusable project context so future tasks are faster and safer
Do Not Use For
- TUnit projects
- MSTest projects
- generic test strategy with no xUnit-specific mechanics
Inputs
- the nearest
AGENTS.md - the test project file and package references
- the active runner model for the test project
Quick Start
- Read the nearest
AGENTS.mdand confirm scope and constraints. - Run this skill's
Workflowthrough theRalph Loopuntil outcomes are acceptable. - Return the
Required Result Formatwith concrete artifacts and verification evidence.
Workflow
- Detect the active xUnit model before changing commands:
xunitusually means v2xunit.v3means v3xunit.runner.visualstudioplusMicrosoft.NET.Test.Sdkusually means VSTest compatibility is enabledTestingPlatformDotnetTestSupportorUseMicrosoftTestingPlatformRunnermeans Microsoft.Testing.Platform is in play
- Read the repo's real
testcommand fromAGENTS.md. If the repo has no explicit command yet, start withdotnet test PROJECT_OR_SOLUTION. - Keep the runner model consistent:
- xUnit v2 usually runs through VSTest
- xUnit v3 can run as a standalone executable with
dotnet run - xUnit v3 can also integrate with Microsoft.Testing.Platform
- do not mix VSTest-only switches into Microsoft.Testing.Platform runs
- Run the narrowest useful scope first:
- one project
- one class
- one trait
- one method
- Prefer
[Theory]for stable data-driven coverage and[Fact]for single-path invariant checks. - Keep
xunit.analyzersenabled when present. Fix analyzer findings instead of muting them casually.
Bootstrap When Missing
If xUnit is requested but not configured:
- Detect current framework first:
rg -n "xunit(\\.v3)?|xunit\\.runner\\.visualstudio|TestingPlatformDotnetTestSupport|UseMicrosoftTestingPlatformRunner|TUnit|MSTest" -g '*.csproj' .
- If the repo currently uses
TUnitorMSTest, do not auto-migrate. Returnstatus: not_applicableunless migration is explicitly requested. - For explicit xUnit adoption, add packages to the target test project:
dotnet add TEST_PROJECT.csproj package xunit.v3- optional VSTest bridge:
dotnet add TEST_PROJECT.csproj package xunit.runner.visualstudio
- Add repo test commands and runner notes to
AGENTS.md. - Run
dotnet test TEST_PROJECT.csprojor repo-defined xUnit command and returnstatus: configuredorstatus: improved.
Deliver
- xUnit tests that match the repo's active xUnit version and runner
- commands that work in local and CI runs
- focused verification before broader suite execution
Validate
- the chosen CLI matches the active runner model
- test filters or focused runs are valid for that runner
- tests use deterministic inputs and assertions
- xUnit-specific analyzers remain active unless the repo documents an exception
Ralph Loop
Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work.
- Plan first (mandatory):
- analyze current state
- define target outcome, constraints, and risks
- write a detailed execution plan
- list final validation skills to run at the end, with order and reason
- Execute one planned step and produce a concrete delta.
- Review the result and capture findings with actionable next fixes.
- Apply fixes in small batches and rerun the relevant checks or review steps.
- Update the plan after each iteration.
- Repeat until outcomes are acceptable or only explicit exceptions remain.
- If a dependency is missing, bootstrap it or return
status: not_applicablewith explicit reason and fallback path.
Required Result Format
status:complete|clean|improved|configured|not_applicable|blockedplan: concise plan and current iteration stepactions_taken: concrete changes madevalidation_skills: final skills run, or skipped with reasonsverification: commands, checks, or review evidence summaryremaining: top unresolved items ornone
For setup-only requests with no execution, return status: configured and exact next commands.
Load References
references/xunit.mdreferences/patterns.mdreferences/anti-patterns.md
Example Requests
- "Run this xUnit suite correctly."
- "Fix our xUnit v3 test command."
- "Add an xUnit regression test and keep CI compatible."
Files (dotnet-skills)
-
references
-
anti-patterns.md 15.8 KB
# xUnit Anti-Patterns Avoid these common testing mistakes. ## Test Interdependence Tests must not depend on execution order or shared mutable state. Bad: ```csharp public class UserServiceTests { private static int _testUserId; [Fact] public void CreateUser_ValidData_ReturnsId() { var service = new UserService(); _testUserId = service.CreateUser("test@example.com"); // sets static state Assert.True(_testUserId > 0); } [Fact] public void GetUser_ExistingId_ReturnsUser() { var service = new UserService(); var user = service.GetUser(_testUserId); // depends on other test running first Assert.NotNull(user); } } ``` Good: ```csharp public class UserServiceTests { [Fact] public void CreateUser_ValidData_ReturnsId() { // Arrange var service = new UserService(); // Act var userId = service.CreateUser("test@example.com"); // Assert Assert.True(userId > 0); } [Fact] public void GetUser_ExistingId_ReturnsUser() { // Arrange var service = new UserService(); var userId = service.CreateUser("test@example.com"); // each test creates its own data // Act var user = service.GetUser(userId); // Assert Assert.NotNull(user); } } ``` ## Excessive Mocking Over-mocking creates brittle tests that verify implementation rather than behavior. Bad: ```csharp public class OrderServiceTests { [Fact] public void PlaceOrder_Valid_CallsAllMethods() { // Arrange var loggerMock = new Mock<ILogger<OrderService>>(); var repoMock = new Mock<IOrderRepository>(); var validatorMock = new Mock<IOrderValidator>(); var pricingMock = new Mock<IPricingService>(); var inventoryMock = new Mock<IInventoryService>(); var notificationMock = new Mock<INotificationService>(); validatorMock.Setup(v => v.Validate(It.IsAny<Order>())).Returns(true); pricingMock.Setup(p => p.CalculateTotal(It.IsAny<Order>())).Returns(100m); inventoryMock.Setup(i => i.Reserve(It.IsAny<Order>())).Returns(true); repoMock.Setup(r => r.Save(It.IsAny<Order>())).Returns(1); var service = new OrderService(loggerMock.Object, repoMock.Object, validatorMock.Object, pricingMock.Object, inventoryMock.Object, notificationMock.Object); // Act service.PlaceOrder(new Order()); // Assert - verifying every internal call creates coupling to implementation validatorMock.Verify(v => v.Validate(It.IsAny<Order>()), Times.Once); pricingMock.Verify(p => p.CalculateTotal(It.IsAny<Order>()), Times.Once); inventoryMock.Verify(i => i.Reserve(It.IsAny<Order>()), Times.Once); repoMock.Verify(r => r.Save(It.IsAny<Order>()), Times.Once); notificationMock.Verify(n => n.Send(It.IsAny<string>()), Times.Once); } } ``` Good: ```csharp public class OrderServiceTests { [Fact] public void PlaceOrder_ValidOrder_ReturnsOrderId() { // Arrange - use real implementations where cheap, mock only external boundaries var repository = new InMemoryOrderRepository(); var notificationService = Substitute.For<INotificationService>(); var service = new OrderService(repository, notificationService); var order = new Order { CustomerId = 1, Items = [new OrderItem { ProductId = 1, Quantity = 1 }] }; // Act var orderId = service.PlaceOrder(order); // Assert - verify observable outcomes Assert.True(orderId > 0); var savedOrder = repository.GetById(orderId); Assert.NotNull(savedOrder); Assert.Equal(OrderStatus.Placed, savedOrder.Status); } } ``` ## Testing Implementation Instead of Behavior Tests should verify what the code does, not how it does it. Bad: ```csharp public class CacheTests { [Fact] public void Get_CacheMiss_CallsUnderlyingStore() { // Arrange var storeMock = new Mock<IDataStore>(); storeMock.Setup(s => s.Get("key")).Returns("value"); var cache = new Cache(storeMock.Object); // Act cache.Get("key"); cache.Get("key"); // Assert - tests internal caching logic, not behavior storeMock.Verify(s => s.Get("key"), Times.Once); } } ``` Good: ```csharp public class CacheTests { [Fact] public void Get_SameKey_ReturnsSameValue() { // Arrange var store = new InMemoryDataStore(); store.Set("key", "value"); var cache = new Cache(store); // Act var result1 = cache.Get("key"); var result2 = cache.Get("key"); // Assert - verifies behavior: caching returns consistent results Assert.Equal("value", result1); Assert.Equal("value", result2); Assert.Same(result1, result2); // if reference equality matters } [Fact] public void Get_AfterExpiry_ReturnsUpdatedValue() { // Arrange var store = new InMemoryDataStore(); store.Set("key", "original"); var timeProvider = new FakeTimeProvider(); var cache = new Cache(store, timeProvider, ttl: TimeSpan.FromMinutes(5)); // Act var result1 = cache.Get("key"); store.Set("key", "updated"); timeProvider.Advance(TimeSpan.FromMinutes(10)); var result2 = cache.Get("key"); // Assert Assert.Equal("original", result1); Assert.Equal("updated", result2); } } ``` ## Ignoring Test Isolation Each test must run in isolation without affecting others. Bad: ```csharp public class ConfigurationTests { [Fact] public void SetGlobalConfig_UpdatesEnvironment() { Environment.SetEnvironmentVariable("APP_MODE", "test"); // pollutes other tests var config = new AppConfiguration(); Assert.Equal("test", config.Mode); } } ``` Good: ```csharp public class ConfigurationTests : IDisposable { private readonly string? _originalValue; public ConfigurationTests() { _originalValue = Environment.GetEnvironmentVariable("APP_MODE"); } [Fact] public void SetGlobalConfig_UpdatesEnvironment() { // Arrange Environment.SetEnvironmentVariable("APP_MODE", "test"); var config = new AppConfiguration(); // Assert Assert.Equal("test", config.Mode); } public void Dispose() { Environment.SetEnvironmentVariable("APP_MODE", _originalValue); } } ``` Even better - inject configuration: ```csharp public class ConfigurationTests { [Fact] public void AppConfiguration_TestMode_ReturnsTestBehavior() { // Arrange var settings = new Dictionary<string, string?> { ["APP_MODE"] = "test" }; var configuration = new ConfigurationBuilder() .AddInMemoryCollection(settings) .Build(); var config = new AppConfiguration(configuration); // Assert Assert.Equal("test", config.Mode); } } ``` ## Non-Deterministic Tests Tests must produce the same result every time. Bad: ```csharp public class TimestampTests { [Fact] public void CreateRecord_SetsTimestamp() { // Arrange var service = new RecordService(); // Act var record = service.CreateRecord("data"); // Assert - fails intermittently when clock rolls over Assert.Equal(DateTime.Now.Date, record.CreatedAt.Date); } } public class RandomTests { [Fact] public void GenerateId_ReturnsUniqueId() { var service = new IdGenerator(); var id1 = service.Generate(); var id2 = service.Generate(); Assert.NotEqual(id1, id2); // usually passes, sometimes fails } } ``` Good: ```csharp public class TimestampTests { [Fact] public void CreateRecord_SetsTimestampFromProvider() { // Arrange var fixedTime = new DateTimeOffset(2024, 1, 15, 10, 30, 0, TimeSpan.Zero); var timeProvider = new FakeTimeProvider(fixedTime); var service = new RecordService(timeProvider); // Act var record = service.CreateRecord("data"); // Assert Assert.Equal(fixedTime, record.CreatedAt); } } public class RandomTests { [Fact] public void GenerateId_WithSeed_ReturnsExpectedId() { // Arrange var randomProvider = new SeededRandomProvider(42); var service = new IdGenerator(randomProvider); // Act var id = service.Generate(); // Assert Assert.Equal("expected-seeded-value", id); } [Fact] public void GenerateId_ReturnsValidFormat() { // Arrange var service = new IdGenerator(); // Act var id = service.Generate(); // Assert - verify format rather than specific value Assert.Matches(@"^[a-f0-9]{32}$", id); } } ``` ## Swallowing Exceptions Tests must not catch exceptions without re-throwing or asserting. Bad: ```csharp public class FileServiceTests { [Fact] public void ReadFile_MissingFile_HandlesGracefully() { try { var service = new FileService(); var content = service.ReadFile("/nonexistent/path"); Assert.NotNull(content); } catch { // test passes either way - hides real failures } } } ``` Good: ```csharp public class FileServiceTests { [Fact] public void ReadFile_MissingFile_ThrowsFileNotFoundException() { // Arrange var service = new FileService(); // Act & Assert var exception = Assert.Throws<FileNotFoundException>( () => service.ReadFile("/nonexistent/path")); Assert.Contains("nonexistent", exception.FileName); } [Fact] public void TryReadFile_MissingFile_ReturnsFalse() { // Arrange var service = new FileService(); // Act var success = service.TryReadFile("/nonexistent/path", out var content); // Assert Assert.False(success); Assert.Null(content); } } ``` ## Magic Numbers and Strings Unexplained literals reduce test clarity. Bad: ```csharp public class DiscountTests { [Fact] public void CalculateDiscount_ReturnsExpected() { var service = new DiscountService(); var discount = service.Calculate(150, 3, true); Assert.Equal(22.5m, discount); } } ``` Good: ```csharp public class DiscountTests { [Fact] public void CalculateDiscount_GoldMemberWithBulkOrder_AppliesCombinedDiscount() { // Arrange const decimal orderTotal = 150m; const int itemCount = 3; const bool isGoldMember = true; const decimal expectedBulkDiscount = 15m; // 10% for 3+ items const decimal expectedMemberDiscount = 7.5m; // 5% gold member bonus const decimal expectedTotalDiscount = 22.5m; var service = new DiscountService(); // Act var discount = service.Calculate(orderTotal, itemCount, isGoldMember); // Assert Assert.Equal(expectedTotalDiscount, discount); } } ``` ## Asserting Too Much or Too Little Each test should verify one logical concept. Bad - too many assertions: ```csharp public class UserRegistrationTests { [Fact] public void RegisterUser_ValidData_EverythingWorks() { var service = new UserService(); var result = service.Register("test@example.com", "password123"); Assert.True(result.Success); Assert.NotNull(result.User); Assert.Equal("test@example.com", result.User.Email); Assert.True(result.User.IsActive); Assert.NotNull(result.User.CreatedAt); Assert.True(result.EmailSent); Assert.Equal(1, service.GetUserCount()); Assert.NotNull(service.GetUserByEmail("test@example.com")); // continues for 20 more assertions } } ``` Bad - too few assertions: ```csharp public class UserRegistrationTests { [Fact] public void RegisterUser_ValidData_Succeeds() { var service = new UserService(); var result = service.Register("test@example.com", "password123"); Assert.True(result.Success); // what about the user? was it actually created? } } ``` Good: ```csharp public class UserRegistrationTests { [Fact] public void RegisterUser_ValidData_CreatesActiveUser() { // Arrange var service = new UserService(); const string email = "test@example.com"; // Act var result = service.Register(email, "password123"); // Assert Assert.True(result.Success); Assert.NotNull(result.User); Assert.Equal(email, result.User.Email); Assert.True(result.User.IsActive); } [Fact] public void RegisterUser_ValidData_SendsWelcomeEmail() { // Arrange var emailService = Substitute.For<IEmailService>(); var service = new UserService(emailService); // Act service.Register("test@example.com", "password123"); // Assert emailService.Received(1).SendWelcomeEmail("test@example.com"); } [Fact] public void RegisterUser_ValidData_PersistsUser() { // Arrange var repository = new InMemoryUserRepository(); var service = new UserService(repository); // Act var result = service.Register("test@example.com", "password123"); // Assert var persistedUser = repository.GetByEmail("test@example.com"); Assert.NotNull(persistedUser); Assert.Equal(result.User.Id, persistedUser.Id); } } ``` ## Async Void Tests xUnit does not properly await `async void` test methods. Bad: ```csharp public class AsyncTests { [Fact] public async void FetchData_ReturnsData() // async void - xUnit won't wait { var service = new DataService(); var data = await service.FetchAsync(); Assert.NotEmpty(data); // may not execute before test completes } } ``` Good: ```csharp public class AsyncTests { [Fact] public async Task FetchData_ReturnsData() // async Task - xUnit awaits properly { // Arrange var service = new DataService(); // Act var data = await service.FetchAsync(); // Assert Assert.NotEmpty(data); } } ``` ## Constructor Abuse Test constructors are for shared setup, not test-specific logic. Bad: ```csharp public class PaymentTests { private readonly PaymentResult _result; public PaymentTests() { var service = new PaymentService(); _result = service.ProcessPayment(new Payment { Amount = 100 }); // runs for every test } [Fact] public void ProcessPayment_ValidAmount_Succeeds() => Assert.True(_result.Success); [Fact] public void ProcessPayment_ValidAmount_ReturnsTransactionId() => Assert.NotNull(_result.TransactionId); } ``` Good: ```csharp public class PaymentTests(PaymentFixture fixture) : IClassFixture<PaymentFixture> { [Fact] public void ProcessPayment_ValidAmount_Succeeds() { // Arrange var service = new PaymentService(fixture.Gateway); var payment = new Payment { Amount = 100 }; // Act var result = service.ProcessPayment(payment); // Assert Assert.True(result.Success); } [Fact] public void ProcessPayment_ValidAmount_ReturnsTransactionId() { // Arrange var service = new PaymentService(fixture.Gateway); var payment = new Payment { Amount = 100 }; // Act var result = service.ProcessPayment(payment); // Assert Assert.NotNull(result.TransactionId); } } public class PaymentFixture { public IPaymentGateway Gateway { get; } = new TestPaymentGateway(); } ``` ## Sources - [xUnit.net Patterns](https://xunit.net/docs/comparisons) - [Unit Testing Best Practices](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) -
patterns.md 9.7 KB
# xUnit Testing Patterns ## Arrange-Act-Assert (AAA) Structure every test method in three distinct phases: ```csharp public class CalculatorTests { [Fact] public void Add_TwoPositiveNumbers_ReturnsSum() { // Arrange var calculator = new Calculator(); var a = 5; var b = 3; // Act var result = calculator.Add(a, b); // Assert Assert.Equal(8, result); } } ``` Keep the phases visually separated. Avoid mixing setup, execution, and verification in the same block. ## Class Fixtures Use class fixtures to share expensive setup across all tests in a class. Primary constructors inject the fixture directly: ```csharp public class DatabaseFixture : IAsyncLifetime { public IDbConnection Connection { get; private set; } = null!; public async Task InitializeAsync() { Connection = new SqliteConnection("Data Source=:memory:"); await Connection.OpenAsync(); await SeedTestDataAsync(); } public async Task DisposeAsync() { await Connection.DisposeAsync(); } private async Task SeedTestDataAsync() { // seed shared test data } } public class UserRepositoryTests(DatabaseFixture fixture) : IClassFixture<DatabaseFixture> { [Fact] public async Task GetUser_ExistingId_ReturnsUser() { // Arrange var repository = new UserRepository(fixture.Connection); // Act var user = await repository.GetUserAsync(1); // Assert Assert.NotNull(user); Assert.Equal("TestUser", user.Name); } } ``` ## Collection Fixtures Share expensive resources across multiple test classes: ```csharp public class IntegrationTestFixture : IAsyncLifetime { public HttpClient Client { get; private set; } = null!; private WebApplicationFactory<Program> _factory = null!; public async Task InitializeAsync() { _factory = new WebApplicationFactory<Program>(); Client = _factory.CreateClient(); await Task.CompletedTask; } public async Task DisposeAsync() { Client.Dispose(); await _factory.DisposeAsync(); } } [CollectionDefinition("Integration")] public class IntegrationTestCollection : ICollectionFixture<IntegrationTestFixture>; [Collection("Integration")] public class OrdersApiTests(IntegrationTestFixture fixture) { [Fact] public async Task GetOrders_ReturnsOk() { // Act var response = await fixture.Client.GetAsync("/api/orders"); // Assert response.EnsureSuccessStatusCode(); } } [Collection("Integration")] public class ProductsApiTests(IntegrationTestFixture fixture) { [Fact] public async Task GetProducts_ReturnsOk() { // Act var response = await fixture.Client.GetAsync("/api/products"); // Assert response.EnsureSuccessStatusCode(); } } ``` ## Theory Data Patterns ### InlineData for Simple Cases ```csharp public class ValidationTests { [Theory] [InlineData("", false)] [InlineData("a", false)] [InlineData("ab", false)] [InlineData("abc", true)] [InlineData("valid-password-123", true)] public void IsValidPassword_VariousInputs_ReturnsExpected(string password, bool expected) { // Arrange var validator = new PasswordValidator(minLength: 3); // Act var result = validator.IsValid(password); // Assert Assert.Equal(expected, result); } } ``` ### MemberData for Complex Objects ```csharp public class OrderProcessorTests { public static TheoryData<Order, decimal> OrderDiscountData => new() { { new Order { Total = 100m, CustomerTier = CustomerTier.Standard }, 0m }, { new Order { Total = 100m, CustomerTier = CustomerTier.Silver }, 5m }, { new Order { Total = 100m, CustomerTier = CustomerTier.Gold }, 10m }, { new Order { Total = 500m, CustomerTier = CustomerTier.Gold }, 75m } }; [Theory] [MemberData(nameof(OrderDiscountData))] public void CalculateDiscount_VariousOrders_ReturnsExpectedDiscount(Order order, decimal expectedDiscount) { // Arrange var processor = new OrderProcessor(); // Act var discount = processor.CalculateDiscount(order); // Assert Assert.Equal(expectedDiscount, discount); } } ``` ### ClassData for Reusable Data Sets ```csharp public class EdgeCaseStringData : TheoryData<string?> { public EdgeCaseStringData() { Add(null); Add(""); Add(" "); Add("\t"); Add("\n"); Add(" \t\n "); } } public class StringUtilityTests { [Theory] [ClassData(typeof(EdgeCaseStringData))] public void IsNullOrWhitespace_EdgeCases_ReturnsTrue(string? input) { // Act var result = string.IsNullOrWhiteSpace(input); // Assert Assert.True(result); } } ``` ## Mocking with NSubstitute Prefer NSubstitute for readable substitute configuration: ```csharp public class OrderServiceTests(ITestOutputHelper output) { [Fact] public async Task PlaceOrder_ValidOrder_SendsNotification() { // Arrange var notificationService = Substitute.For<INotificationService>(); var orderRepository = Substitute.For<IOrderRepository>(); orderRepository.SaveAsync(Arg.Any<Order>()).Returns(Task.FromResult(true)); var service = new OrderService(orderRepository, notificationService); var order = new Order { CustomerId = 1, Items = [new OrderItem { ProductId = 1, Quantity = 2 }] }; // Act await service.PlaceOrderAsync(order); // Assert await notificationService.Received(1).SendOrderConfirmationAsync(Arg.Is<Order>(o => o.CustomerId == 1)); output.WriteLine("Order placed and notification sent"); } [Fact] public async Task PlaceOrder_RepositoryFails_ThrowsException() { // Arrange var notificationService = Substitute.For<INotificationService>(); var orderRepository = Substitute.For<IOrderRepository>(); orderRepository.SaveAsync(Arg.Any<Order>()).ThrowsAsync(new DataException("Connection failed")); var service = new OrderService(orderRepository, notificationService); var order = new Order { CustomerId = 1 }; // Act & Assert await Assert.ThrowsAsync<DataException>(() => service.PlaceOrderAsync(order)); await notificationService.DidNotReceive().SendOrderConfirmationAsync(Arg.Any<Order>()); } } ``` ## Mocking with Moq Use Moq when the project already depends on it: ```csharp public class PaymentProcessorTests { [Fact] public async Task ProcessPayment_ValidCard_ReturnsSuccess() { // Arrange var gatewayMock = new Mock<IPaymentGateway>(); gatewayMock .Setup(g => g.ChargeAsync(It.IsAny<string>(), It.Is<decimal>(d => d > 0))) .ReturnsAsync(new PaymentResult { Success = true, TransactionId = "TX123" }); var processor = new PaymentProcessor(gatewayMock.Object); // Act var result = await processor.ProcessPaymentAsync("4111111111111111", 99.99m); // Assert Assert.True(result.Success); Assert.Equal("TX123", result.TransactionId); gatewayMock.Verify(g => g.ChargeAsync("4111111111111111", 99.99m), Times.Once); } } ``` ## Output and Diagnostics Use `ITestOutputHelper` for test diagnostics: ```csharp public class DiagnosticTests(ITestOutputHelper output) { [Fact] public void ComplexCalculation_LargeInput_CompletesWithinTimeout() { // Arrange var calculator = new ComplexCalculator(); var input = GenerateLargeInput(); output.WriteLine($"Testing with input size: {input.Length}"); var stopwatch = Stopwatch.StartNew(); // Act var result = calculator.Process(input); // Assert stopwatch.Stop(); output.WriteLine($"Completed in {stopwatch.ElapsedMilliseconds}ms"); Assert.True(stopwatch.ElapsedMilliseconds < 5000, "Calculation took too long"); } private static int[] GenerateLargeInput() => Enumerable.Range(0, 100_000).ToArray(); } ``` ## Async Test Patterns xUnit handles async tests natively: ```csharp public class AsyncServiceTests { [Fact] public async Task FetchData_ValidEndpoint_ReturnsData() { // Arrange var service = new DataService(); // Act var data = await service.FetchDataAsync("/api/items"); // Assert Assert.NotEmpty(data); } [Fact] public async Task FetchData_Timeout_ThrowsOperationCanceledException() { // Arrange var service = new DataService(); using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(1)); // Act & Assert await Assert.ThrowsAsync<OperationCanceledException>( () => service.FetchDataAsync("/api/slow-endpoint", cts.Token)); } } ``` ## Trait-Based Organization Use traits sparingly for CI filtering: ```csharp public class IntegrationTests { [Fact] [Trait("Category", "Integration")] [Trait("Database", "SqlServer")] public async Task CreateUser_ValidData_PersistsToDatabase() { // integration test implementation } } public class UnitTests { [Fact] [Trait("Category", "Unit")] public void ValidateEmail_InvalidFormat_ReturnsFalse() { // unit test implementation } } ``` Run filtered: ```bash dotnet test --filter "Category=Unit" dotnet test --filter "Category!=Integration" ``` ## Sources - [xUnit.net Theory Data](https://xunit.net/docs/getting-started/v3/theory-data) - [xUnit.net Shared Context](https://xunit.net/docs/shared-context) - [NSubstitute Documentation](https://nsubstitute.github.io/help/getting-started/) -
xunit.md 3.1 KB
# xUnit in MCAF ## Open/Free Status - open source - free to use ## Install xUnit v3 package setup: ```bash dotnet add package xunit.v3 ``` VSTest compatibility package when the repo intentionally uses that runner: ```bash dotnet add package xunit.runner.visualstudio ``` ## Verify First Before adding packages, check what the repo already references: ```bash rg -n "xunit(\\.v3)?|xunit\\.runner\\.visualstudio|TestingPlatformDotnetTestSupport|UseMicrosoftTestingPlatformRunner" -g '*.csproj' . ``` Use this reference when the repository already chose xUnit and you need framework-specific commands, package checks, or CI guardrails. ## Detect the xUnit Model Use the project file as the source of truth: ```bash rg -n "xunit\\.v3|xunit.runner.visualstudio|Microsoft\\.NET\\.Test\\.Sdk|TestingPlatformDotnetTestSupport|UseMicrosoftTestingPlatformRunner" -g '*.csproj' . ``` Typical markers: - xUnit v2: `xunit` - xUnit v3: `xunit.v3` - VSTest compatibility: `xunit.runner.visualstudio` and `Microsoft.NET.Test.Sdk` - Microsoft.Testing.Platform support: `TestingPlatformDotnetTestSupport` or `UseMicrosoftTestingPlatformRunner` ## Common Commands Start with the repo's `test` command from `AGENTS.md`. If the repo has not documented one yet, these are the safe defaults: ```bash dotnet test MySolution.sln dotnet test tests/MyProject.Tests/MyProject.Tests.csproj dotnet test tests/MyProject.Tests/MyProject.Tests.csproj --no-build ``` Focused VSTest-style run: ```bash dotnet test tests/MyProject.Tests/MyProject.Tests.csproj --filter "FullyQualifiedName~Namespace.TypeName" ``` xUnit v3 standalone runner: ```bash dotnet run --project tests/MyProject.Tests/MyProject.Tests.csproj ``` xUnit v3 with Microsoft.Testing.Platform-style class filtering: ```bash dotnet run --project tests/MyProject.Tests/MyProject.Tests.csproj -- --filter-class Namespace.TypeName ``` If the project enables `TestingPlatformDotnetTestSupport`, `dotnet test` can forward into Microsoft.Testing.Platform. Keep those switches consistent with the runner the project actually uses. ## CI Notes - Use one runner model per project. Do not mix VSTest-only flags and Microsoft.Testing.Platform flags in the same command. - Build first, then use `--no-build` for repeat test runs. - Keep coverage driver aligned with the runner: - VSTest: `coverlet.collector` or `--collect:"XPlat Code Coverage"` - Microsoft.Testing.Platform: `coverlet.MTP` - Keep xUnit analyzers on: - xUnit v2 2.3+ usually brings them through the main `xunit` package - xUnit v3 brings analyzer guidance through the main package set unless the repo split packages explicitly ## Good Defaults - prefer `[Theory]` plus stable inline or member data for variant-heavy behavior - use traits only when the repo already relies on them for filtering - avoid runner rewrites in the same change as behavior work unless the current command is already broken ## Sources - [xUnit.net v3 getting started](https://xunit.net/docs/getting-started/v3/getting-started) - [xUnit.net v3 Microsoft Testing Platform support](https://xunit.net/docs/getting-started/v3/microsoft-testing-platform) - [xunit/xunit.analyzers](https://github.com/xunit/xunit.analyzers)
-
-
SKILL.md 5.1 KB
--- name: dotnet-xunit version: "1.0.0" category: "Testing" description: "Write, run, or repair .NET tests that use xUnit. Use when a repo uses `xunit`, `xunit.v3`, `[Fact]`, `[Theory]`, or `xunit.runner.visualstudio`, and you need the right CLI, package, and runner guidance for xUnit on VSTest or Microsoft.Testing.Platform." compatibility: "Requires a .NET solution or project with xUnit packages; respects the repo's `AGENTS.md` commands first." --- # xUnit.net ## Trigger On - the repo uses xUnit v2 or xUnit v3 - you need to add, run, debug, or repair xUnit tests - the team is unsure whether a project is using VSTest or Microsoft.Testing.Platform ## Value - produce a concrete project delta: code, docs, config, tests, CI, or review artifact - reduce ambiguity through explicit planning, verification, and final validation skills - leave reusable project context so future tasks are faster and safer ## Do Not Use For - TUnit projects - MSTest projects - generic test strategy with no xUnit-specific mechanics ## Inputs - the nearest `AGENTS.md` - the test project file and package references - the active runner model for the test project ## Quick Start 1. Read the nearest `AGENTS.md` and confirm scope and constraints. 2. Run this skill's `Workflow` through the `Ralph Loop` until outcomes are acceptable. 3. Return the `Required Result Format` with concrete artifacts and verification evidence. ## Workflow 1. Detect the active xUnit model before changing commands: - `xunit` usually means v2 - `xunit.v3` means v3 - `xunit.runner.visualstudio` plus `Microsoft.NET.Test.Sdk` usually means VSTest compatibility is enabled - `TestingPlatformDotnetTestSupport` or `UseMicrosoftTestingPlatformRunner` means Microsoft.Testing.Platform is in play 2. Read the repo's real `test` command from `AGENTS.md`. If the repo has no explicit command yet, start with `dotnet test PROJECT_OR_SOLUTION`. 3. Keep the runner model consistent: - xUnit v2 usually runs through VSTest - xUnit v3 can run as a standalone executable with `dotnet run` - xUnit v3 can also integrate with Microsoft.Testing.Platform - do not mix VSTest-only switches into Microsoft.Testing.Platform runs 4. Run the narrowest useful scope first: - one project - one class - one trait - one method 5. Prefer `[Theory]` for stable data-driven coverage and `[Fact]` for single-path invariant checks. 6. Keep `xunit.analyzers` enabled when present. Fix analyzer findings instead of muting them casually. ## Bootstrap When Missing If xUnit is requested but not configured: 1. Detect current framework first: - `rg -n "xunit(\\.v3)?|xunit\\.runner\\.visualstudio|TestingPlatformDotnetTestSupport|UseMicrosoftTestingPlatformRunner|TUnit|MSTest" -g '*.csproj' .` 2. If the repo currently uses `TUnit` or `MSTest`, do not auto-migrate. Return `status: not_applicable` unless migration is explicitly requested. 3. For explicit xUnit adoption, add packages to the target test project: - `dotnet add TEST_PROJECT.csproj package xunit.v3` - optional VSTest bridge: `dotnet add TEST_PROJECT.csproj package xunit.runner.visualstudio` 4. Add repo test commands and runner notes to `AGENTS.md`. 5. Run `dotnet test TEST_PROJECT.csproj` or repo-defined xUnit command and return `status: configured` or `status: improved`. ## Deliver - xUnit tests that match the repo's active xUnit version and runner - commands that work in local and CI runs - focused verification before broader suite execution ## Validate - the chosen CLI matches the active runner model - test filters or focused runs are valid for that runner - tests use deterministic inputs and assertions - xUnit-specific analyzers remain active unless the repo documents an exception ## Ralph Loop Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work. 1. Plan first (mandatory): - analyze current state - define target outcome, constraints, and risks - write a detailed execution plan - list final validation skills to run at the end, with order and reason 2. Execute one planned step and produce a concrete delta. 3. Review the result and capture findings with actionable next fixes. 4. Apply fixes in small batches and rerun the relevant checks or review steps. 5. Update the plan after each iteration. 6. Repeat until outcomes are acceptable or only explicit exceptions remain. 7. If a dependency is missing, bootstrap it or return `status: not_applicable` with explicit reason and fallback path. ### Required Result Format - `status`: `complete` | `clean` | `improved` | `configured` | `not_applicable` | `blocked` - `plan`: concise plan and current iteration step - `actions_taken`: concrete changes made - `validation_skills`: final skills run, or skipped with reasons - `verification`: commands, checks, or review evidence summary - `remaining`: top unresolved items or `none` For setup-only requests with no execution, return `status: configured` and exact next commands. ## Load References - `references/xunit.md` - `references/patterns.md` - `references/anti-patterns.md` ## Example Requests - "Run this xUnit suite correctly." - "Fix our xUnit v3 test command." - "Add an xUnit regression test and keep CI compatible."
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.