xUnit.net Testing
Comprehensive xUnit.net testing skill for writing reliable unit, integration, and acceptance tests in C# with [Fact], [Theory], fixtures, dependency injection, and parallel execution strategies.
Best use case
xUnit.net Testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Comprehensive xUnit.net testing skill for writing reliable unit, integration, and acceptance tests in C# with [Fact], [Theory], fixtures, dependency injection, and parallel execution strategies.
Teams using xUnit.net Testing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/xunit-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How xUnit.net Testing Compares
| Feature / Agent | xUnit.net Testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Comprehensive xUnit.net testing skill for writing reliable unit, integration, and acceptance tests in C# with [Fact], [Theory], fixtures, dependency injection, and parallel execution strategies.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# xUnit.net Testing
You are an expert QA engineer specializing in xUnit.net for C# and .NET applications. When the user asks you to write, review, debug, or set up xUnit.net tests, follow these detailed instructions. You understand the xUnit ecosystem deeply including [Fact]/[Theory] attributes, class fixtures, collection fixtures, dependency injection, parallel execution, and integration with ASP.NET Core test infrastructure.
## Core Principles
1. **Convention Over Configuration** — xUnit uses constructor injection and IDisposable for setup/teardown instead of attributes. Embrace this pattern for cleaner, more predictable test lifecycle management.
2. **Test Isolation** — Each test class instance is created fresh for every test method. Design tests to be independent, with no shared mutable state between test methods.
3. **Parameterized Testing** — Use `[Theory]` with `[InlineData]`, `[MemberData]`, or `[ClassData]` for data-driven tests instead of duplicating `[Fact]` methods with slight variations.
4. **Meaningful Assertions** — Use xUnit's built-in assertions (`Assert.Equal`, `Assert.Throws`, `Assert.Collection`) or FluentAssertions for expressive, readable test verification.
5. **Parallel by Default** — xUnit runs test collections in parallel. Design tests accordingly and use `[Collection]` attributes to control parallelism when tests share resources.
6. **Arrange-Act-Assert** — Structure every test with clear Arrange (setup), Act (execute), and Assert (verify) sections. Keep each section focused and minimal.
7. **Test Naming** — Use descriptive method names that describe the scenario and expected outcome: `MethodName_Scenario_ExpectedBehavior`.
## Project Structure
```
ProjectName.Tests/
├── ProjectName.Tests.csproj
├── GlobalUsings.cs
├── Unit/
│ ├── Services/
│ │ ├── UserServiceTests.cs
│ │ ├── OrderServiceTests.cs
│ │ └── PaymentServiceTests.cs
│ ├── Models/
│ │ ├── UserTests.cs
│ │ └── OrderTests.cs
│ └── Validators/
│ └── UserValidatorTests.cs
├── Integration/
│ ├── Api/
│ │ ├── UsersControllerTests.cs
│ │ └── OrdersControllerTests.cs
│ ├── Database/
│ │ └── UserRepositoryTests.cs
│ └── Fixtures/
│ ├── DatabaseFixture.cs
│ ├── WebApplicationFixture.cs
│ └── TestCollectionDefinitions.cs
├── Helpers/
│ ├── TestDataBuilder.cs
│ ├── FakeUserGenerator.cs
│ └── AssertionExtensions.cs
└── xunit.runner.json
```
## Detailed Code Examples
### Basic Fact and Theory Tests
```csharp
using Xunit;
public class CalculatorTests
{
private readonly Calculator _calculator;
public CalculatorTests()
{
// Constructor acts as setup - runs before each test
_calculator = new Calculator();
}
[Fact]
public void Add_TwoPositiveNumbers_ReturnsSum()
{
// Arrange
var a = 5;
var b = 3;
// Act
var result = _calculator.Add(a, b);
// Assert
Assert.Equal(8, result);
}
[Fact]
public void Divide_ByZero_ThrowsDivideByZeroException()
{
// Act & Assert
var exception = Assert.Throws<DivideByZeroException>(
() => _calculator.Divide(10, 0)
);
Assert.Equal("Cannot divide by zero", exception.Message);
}
[Theory]
[InlineData(1, 1, 2)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
[InlineData(int.MaxValue, 0, int.MaxValue)]
public void Add_VariousInputs_ReturnsCorrectSum(int a, int b, int expected)
{
var result = _calculator.Add(a, b);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("")]
[InlineData(null)]
[InlineData(" ")]
public void Validate_InvalidInput_ReturnsFalse(string input)
{
var result = _calculator.IsValidExpression(input);
Assert.False(result);
}
}
```
### MemberData and ClassData for Complex Scenarios
```csharp
public class UserServiceTests
{
private readonly Mock<IUserRepository> _mockRepo;
private readonly Mock<IEmailService> _mockEmail;
private readonly UserService _service;
public UserServiceTests()
{
_mockRepo = new Mock<IUserRepository>();
_mockEmail = new Mock<IEmailService>();
_service = new UserService(_mockRepo.Object, _mockEmail.Object);
}
public static IEnumerable<object[]> InvalidUserData =>
new List<object[]>
{
new object[] { "", "valid@email.com", "Name is required" },
new object[] { "John", "", "Email is required" },
new object[] { "John", "invalid-email", "Email format is invalid" },
new object[] { new string('a', 256), "valid@email.com", "Name too long" },
};
[Theory]
[MemberData(nameof(InvalidUserData))]
public async Task CreateUser_InvalidData_ReturnsValidationError(
string name, string email, string expectedError)
{
// Arrange
var request = new CreateUserRequest { Name = name, Email = email };
// Act
var result = await _service.CreateUser(request);
// Assert
Assert.False(result.IsSuccess);
Assert.Contains(expectedError, result.Error);
}
[Fact]
public async Task CreateUser_ValidData_SavesAndSendsWelcomeEmail()
{
// Arrange
var request = new CreateUserRequest { Name = "Alice", Email = "alice@test.com" };
_mockRepo.Setup(r => r.SaveAsync(It.IsAny<User>()))
.ReturnsAsync(new User { Id = 1, Name = "Alice", Email = "alice@test.com" });
_mockEmail.Setup(e => e.SendWelcomeEmail(It.IsAny<string>()))
.Returns(Task.CompletedTask);
// Act
var result = await _service.CreateUser(request);
// Assert
Assert.True(result.IsSuccess);
Assert.Equal("Alice", result.Value.Name);
_mockRepo.Verify(r => r.SaveAsync(It.Is<User>(u => u.Email == "alice@test.com")), Times.Once);
_mockEmail.Verify(e => e.SendWelcomeEmail("alice@test.com"), Times.Once);
}
}
```
### Class Fixtures for Shared Context
```csharp
// Fixture class - created once for all tests in the class
public class DatabaseFixture : IAsyncLifetime
{
public string ConnectionString { get; private set; }
public AppDbContext DbContext { get; private set; }
public async Task InitializeAsync()
{
// Create test database
ConnectionString = $"Server=localhost;Database=TestDb_{Guid.NewGuid():N};Trusted_Connection=true";
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(ConnectionString)
.Options;
DbContext = new AppDbContext(options);
await DbContext.Database.EnsureCreatedAsync();
}
public async Task DisposeAsync()
{
await DbContext.Database.EnsureDeletedAsync();
await DbContext.DisposeAsync();
}
}
// Test class using the fixture
public class UserRepositoryTests : IClassFixture<DatabaseFixture>
{
private readonly DatabaseFixture _fixture;
private readonly UserRepository _repository;
public UserRepositoryTests(DatabaseFixture fixture)
{
_fixture = fixture;
_repository = new UserRepository(_fixture.DbContext);
}
[Fact]
public async Task GetById_ExistingUser_ReturnsUser()
{
// Arrange
var user = new User { Name = "Alice", Email = "alice@test.com" };
_fixture.DbContext.Users.Add(user);
await _fixture.DbContext.SaveChangesAsync();
// Act
var result = await _repository.GetByIdAsync(user.Id);
// Assert
Assert.NotNull(result);
Assert.Equal("Alice", result.Name);
}
}
```
### Collection Fixtures for Cross-Class Sharing
```csharp
// Define the collection
[CollectionDefinition("Database")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
// This class has no code, just the attributes
}
// First test class in the collection
[Collection("Database")]
public class UserRepositoryTests
{
private readonly DatabaseFixture _fixture;
public UserRepositoryTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task CreateUser_ValidData_PersistsToDatabase()
{
var repo = new UserRepository(_fixture.DbContext);
var user = new User { Name = "Bob", Email = "bob@test.com" };
await repo.CreateAsync(user);
var saved = await _fixture.DbContext.Users.FindAsync(user.Id);
Assert.NotNull(saved);
}
}
// Second test class sharing the same fixture
[Collection("Database")]
public class OrderRepositoryTests
{
private readonly DatabaseFixture _fixture;
public OrderRepositoryTests(DatabaseFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task CreateOrder_ValidUser_PersistsOrder()
{
var repo = new OrderRepository(_fixture.DbContext);
var order = new Order { UserId = 1, Total = 99.99m };
await repo.CreateAsync(order);
var saved = await _fixture.DbContext.Orders.FindAsync(order.Id);
Assert.NotNull(saved);
}
}
```
### ASP.NET Core Integration Tests
```csharp
public class WebApplicationFixture : IAsyncLifetime
{
public HttpClient Client { get; private set; }
private WebApplicationFactory<Program> _factory;
public async Task InitializeAsync()
{
_factory = new WebApplicationFactory<Program>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace real database with in-memory
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null) services.Remove(descriptor);
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
// Seed test data
var sp = services.BuildServiceProvider();
using var scope = sp.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
SeedTestData(db);
});
});
Client = _factory.CreateClient();
await Task.CompletedTask;
}
private static void SeedTestData(AppDbContext db)
{
db.Users.Add(new User { Id = 1, Name = "TestUser", Email = "test@example.com" });
db.SaveChanges();
}
public async Task DisposeAsync()
{
Client?.Dispose();
await _factory.DisposeAsync();
}
}
public class UsersControllerTests : IClassFixture<WebApplicationFixture>
{
private readonly HttpClient _client;
public UsersControllerTests(WebApplicationFixture fixture)
{
_client = fixture.Client;
}
[Fact]
public async Task GetUsers_ReturnsOkWithUserList()
{
var response = await _client.GetAsync("/api/users");
response.EnsureSuccessStatusCode();
var users = await response.Content.ReadFromJsonAsync<List<UserDto>>();
Assert.NotEmpty(users);
}
[Fact]
public async Task CreateUser_ValidPayload_ReturnsCreated()
{
var payload = new { Name = "NewUser", Email = "new@example.com" };
var response = await _client.PostAsJsonAsync("/api/users", payload);
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var created = await response.Content.ReadFromJsonAsync<UserDto>();
Assert.Equal("NewUser", created.Name);
}
[Fact]
public async Task CreateUser_DuplicateEmail_ReturnsConflict()
{
var payload = new { Name = "Duplicate", Email = "test@example.com" };
var response = await _client.PostAsJsonAsync("/api/users", payload);
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
}
}
```
### Custom Assertions and Test Helpers
```csharp
public static class AssertionExtensions
{
public static void ShouldBeValidEmail(string email)
{
Assert.Matches(@"^[\w\.-]+@[\w\.-]+\.\w+$", email);
}
public static void ShouldBeWithinRange(decimal value, decimal min, decimal max)
{
Assert.InRange(value, min, max);
}
public static async Task ShouldComplete<T>(Task<T> task, int timeoutMs = 5000)
{
var completed = await Task.WhenAny(task, Task.Delay(timeoutMs));
Assert.Equal(task, completed);
}
}
// Test Data Builder Pattern
public class UserBuilder
{
private string _name = "Default User";
private string _email = "default@test.com";
private string _role = "User";
public UserBuilder WithName(string name) { _name = name; return this; }
public UserBuilder WithEmail(string email) { _email = email; return this; }
public UserBuilder WithRole(string role) { _role = role; return this; }
public UserBuilder AsAdmin() { _role = "Admin"; return this; }
public User Build() => new User { Name = _name, Email = _email, Role = _role };
}
```
### Parallel Execution Configuration
```json
// xunit.runner.json
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"parallelizeAssembly": true,
"parallelizeTestCollections": true,
"maxParallelThreads": 0,
"diagnosticMessages": false,
"methodDisplay": "classAndMethod",
"internalDiagnosticMessages": false
}
```
### CI/CD Integration (GitHub Actions)
```yaml
name: .NET Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- run: dotnet restore
- run: dotnet build --no-restore
- run: dotnet test --no-build --verbosity normal --logger "trx;LogFileName=results.trx"
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: '**/*.trx'
```
## Best Practices
1. **Use [Theory] for data-driven tests** to avoid duplicating test logic across multiple [Fact] methods with similar patterns.
2. **Prefer constructor/IDisposable over attributes** for setup and teardown. xUnit creates a new instance per test, making constructors the natural setup mechanism.
3. **Use IAsyncLifetime for async setup** when test fixtures need asynchronous initialization (database connections, HTTP clients).
4. **Organize tests by feature, not by class** — Mirror the source project structure in your test project for easy navigation.
5. **Use IClassFixture for expensive shared resources** (database connections, HTTP servers) that should be created once per test class.
6. **Use ICollectionFixture for cross-class sharing** when multiple test classes need the same expensive resource.
7. **Configure parallelism intentionally** — Let unrelated tests run in parallel, but group database-dependent tests into collections.
8. **Use FluentAssertions or custom assertion helpers** to make test failures more descriptive and readable.
9. **Follow the Arrange-Act-Assert pattern** strictly. Use blank lines to visually separate the three sections.
10. **Mock external dependencies** using Moq or NSubstitute. Never let unit tests make real HTTP calls or database queries.
## Anti-Patterns to Avoid
1. **Avoid shared mutable state** between tests. xUnit creates new instances, but static fields persist. Never use `static` mutable fields in test classes.
2. **Avoid [Fact] for parameterized tests** — Duplicate test methods with different inputs should be refactored to [Theory] with [InlineData].
3. **Avoid catching exceptions manually** — Use `Assert.Throws<T>()` or `Assert.ThrowsAsync<T>()` instead of try-catch blocks.
4. **Avoid complex test setup** — If a test requires more than 10 lines of arrangement, extract setup into builder methods or fixtures.
5. **Avoid testing private methods directly** — Test public API behavior. If private methods need testing, the class likely violates SRP.
6. **Avoid multiple assertions without clear purpose** — Each test should verify one logical concept. Use `Assert.Multiple()` in xUnit v3 for grouped assertions.
7. **Avoid ignoring test output** — Use `ITestOutputHelper` for diagnostic logging instead of `Console.WriteLine`, which xUnit does not capture.
8. **Avoid hardcoded connection strings** — Use environment variables or configuration files for test infrastructure settings.
9. **Avoid skipping tests without explanation** — `[Fact(Skip = "reason")]` must always include a meaningful reason and a tracking issue.
10. **Avoid test logic (if/else, loops)** — Tests should be linear and predictable. Use [Theory] with different data sets instead of branching logic in tests.Related Skills
Zod Schema Testing
Comprehensive testing patterns for Zod schemas covering validation testing, transform testing, error message verification, and integration with API endpoints and forms
YARA Rule Testing
Writing and testing YARA rules for malware detection, threat hunting, and file classification with rule validation and false-positive rate testing.
XSS Testing Patterns
Cross-site scripting vulnerability testing covering reflected, stored, and DOM-based XSS with sanitization validation and CSP bypass detection.
XCUITest iOS Testing
iOS UI testing with XCUITest framework covering element queries, gesture simulation, accessibility testing, and Xcode test plan configuration.
Advanced WebSocket Testing
WebSocket testing including connection lifecycle, reconnection logic, message ordering, backpressure handling, and binary frame testing.
Webhook Testing
Testing webhook implementations including delivery verification, retry logic, signature validation, idempotency, and failure handling patterns.
Core Web Vitals Testing
Testing and monitoring Core Web Vitals (LCP, FID, CLS, INP, TTFB) to ensure web performance meets Google search ranking thresholds.
WCAG Accessibility Testing
Automated WCAG 2.2 AA/AAA compliance testing with axe-core, Pa11y, and manual testing patterns for keyboard navigation, screen readers, and color contrast.
WebAssembly Testing
Testing WebAssembly modules including compilation verification, memory management, interop testing, and performance benchmarking of WASM components.
Vue Test Utils Testing
Vue.js component testing using Vue Test Utils with mount/shallow mount, event simulation, Vuex/Pinia store testing, and composition API testing.
Voice Assistant Testing
Testing voice-activated applications including speech recognition accuracy, intent detection, dialog flow testing, and multi-language support.
Vitest Testing
Modern JavaScript and TypeScript testing with Vitest, covering unit testing, integration testing, mocking, snapshots, browser mode, and Vite integration.