Introduction
xUnit provides [Fact] for parameterless tests and [Theory] with [InlineData] or [MemberData] for parameterized tests. When theory tests are silently skipped, marked as skipped, or facts are not discovered, the cause is typically a type mismatch in InlineData parameters, incorrect MemberData member signatures, missing test adapter packages, async setup conflicts, or test class constructor failures. xUnit does not fail loudly for many of these issues — it simply marks the test as skipped, making debugging challenging.
Symptoms
- Test shows as "Skipped" in test runner output
- Theory test with InlineData never executes
- MemberData test shows "0 test cases" discovered
- Fact test not listed in test explorer
- "No test is available" message from dotnet test
- Test class constructor throws but no error shown
Error output: ``` [xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.5.4.1+... [xUnit.net 00:00:01.23] Discovering: MyProject.Tests [xUnit.net 00:00:01.45] Discovered: MyProject.Tests (0 test cases) [xUnit.net 00:00:01.46] Starting: MyProject.Tests [xUnit.net 00:00:01.47] Finished: MyProject.Tests
Skipped: MyProject.Tests.UserServiceTests.ShouldCreateUser(name: "John") Reason: Test skipped by xUnit ```
Common Causes
- InlineData parameter type does not match theory method parameter type
- MemberData references non-static member or wrong member type
- Test class has no parameterless constructor and DI not configured
- Async constructor or async class initialization
- Missing
xunit.runner.visualstudiopackage - Test method parameters use types not supported by InlineData (e.g., complex objects)
[MemberData]member returnsIEnumerable<object[]>but with wrong inner type
Step-by-Step Fix
- 1.Fix InlineData type mismatches:
- 2.```csharp
- 3.public class StringUtilitiesTests
- 4.{
- 5.// CORRECT - types match exactly
- 6.[Theory]
- 7.[InlineData("hello", true)]
- 8.[InlineData("", false)]
- 9.[InlineData(null, false)] // null works for reference types
- 10.public void IsNonEmpty_ShouldReturnExpected(string input, bool expected)
- 11.{
- 12.var result = StringUtilities.IsNonEmpty(input);
- 13.Assert.Equal(expected, result);
- 14.}
// WRONG - enum InlineData passed to string parameter [Theory] [InlineData(StringComparison.OrdinalIgnoreCase)] // enum, not string! public void Test_Bad(string comparison) { }
// CORRECT - cast or convert in InlineData [Theory] [InlineData("OrdinalIgnoreCase")] public void Test_Good(string comparisonName) { var comparison = Enum.Parse<StringComparison>(comparisonName); // ... }
// InlineData does NOT support complex types - use MemberData instead [Theory] [MemberData(nameof(GetUserTestData))] public void CreateUser_ShouldSucceed(UserDto user, bool shouldSucceed) { // ... }
public static IEnumerable<object[]> GetUserTestData() { yield return new object[] { new UserDto { Name = "John", Email = "john@test.com" }, true }; yield return new object[] { new UserDto { Name = "", Email = "invalid" }, false }; } } ```
- 1.Fix MemberData signature requirements:
- 2.```csharp
- 3.public class PaymentProcessorTests
- 4.{
- 5.// CORRECT - static property returning IEnumerable<object[]>
- 6.public static IEnumerable<object[]> ValidPayments =>
- 7.new[]
- 8.{
- 9.new object[] { new Payment(100m, "USD"), true },
- 10.new object[] { new Payment(0.01m, "EUR"), true },
- 11.new object[] { new Payment(999999m, "GBP"), true },
- 12.};
[Theory] [MemberData(nameof(ValidPayments))] public void ProcessPayment_ValidAmount_ShouldSucceed( Payment payment, bool expectedSuccess) { var result = _processor.Process(payment); Assert.Equal(expectedSuccess, result.Success); }
// CORRECT - static method with parameters public static IEnumerable<object[]> GetPaymentsForCurrency(string currency) => new[] { new object[] { new Payment(100m, currency) }, new object[] { new Payment(200m, currency) }, };
[Theory] [MemberData(nameof(GetPaymentsForCurrency), "USD")] [MemberData(nameof(GetPaymentsForCurrency), "EUR")] public void ProcessPayment_ByCurrency_ShouldWork(Payment payment) { var result = _processor.Process(payment); Assert.True(result.Success); }
// WRONG - non-static member public IEnumerable<object[]> BadData => ... // Must be static!
// WRONG - returns wrong type public static IEnumerable<Payment> WrongType => ... // Must be object[]! } ```
- 1.Fix test discovery and class constructor issues:
- 2.```csharp
- 3.// Check .csproj has correct test SDK
- 4.// <Project Sdk="Microsoft.NET.Sdk">
- 5.// <PropertyGroup>
- 6.// <TargetFramework>net8.0</TargetFramework>
- 7.// <IsPackable>false</IsPackable>
- 8.// </PropertyGroup>
- 9.//
- 10.// <ItemGroup>
- 11.// <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
- 12.// <PackageReference Include="xunit" Version="2.7.0" />
- 13.// <PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
- 14.// <PrivateAssets>all</PrivateAssets>
- 15.// <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
- 16.// </PackageReference>
- 17.// </ItemGroup>
- 18.// </Project>
// Test class with constructor - must not throw public class UserServiceTests : IDisposable { private readonly IUserService _userService; private readonly TestDatabase _database;
public UserServiceTests() { // Constructor must NOT throw - if it does, ALL tests are skipped try { _database = new TestDatabase(); _userService = new UserService(_database); } catch (Exception ex) { // Log but don't throw - otherwise all tests skip silently Console.WriteLine($"Test setup failed: {ex.Message}"); throw; // This will skip ALL tests in this class } }
public void Dispose() { _database?.Dispose(); } }
// Use IClassFixture for shared setup that can fail gracefully public class DatabaseFixture : IDisposable { public TestDatabase Database { get; }
public DatabaseFixture() { Database = new TestDatabase(); Database.Initialize(); }
public void Dispose() => Database.Dispose(); }
public class UserServiceTests : IClassFixture<DatabaseFixture> { private readonly DatabaseFixture _fixture;
public UserServiceTests(DatabaseFixture fixture) { _fixture = fixture; }
[Fact] public void ShouldConnectToDatabase() { Assert.NotNull(_fixture.Database.Connection); } } ```
- 1.Debug test discovery issues:
- 2.```bash
- 3.# List discovered tests without running
- 4.dotnet test --list-tests
# Run with verbose output to see discovery dotnet test --logger "console;verbosity=detailed"
# Run specific test by fully qualified name dotnet test --filter "FullyQualifiedName~UserServiceTests.ShouldCreateUser"
# Check if test adapter is loaded dotnet test --diag:test-diag.log # Then inspect test-diag.log for discovery errors ```
Prevention
- Use
[ClassData]for complex test data that requires setup logic - Keep MemberData members as
public staticand returnIEnumerable<object[]> - Use inline
[InlineData]only for primitive types, strings, enums, and Type - Add a test discovery CI step that asserts minimum test count
- Avoid throwing in test constructors — use fixtures for expensive setup
- Keep test projects separate from source projects to avoid SDK conflicts
- Run
dotnet test --list-testsin CI to catch discovery issues early - Use xUnit's
[Collection]attribute for tests that share expensive resources