Optivem.Testing is an xUnit extension for channel-based data-driven testing. It enables you to run the same tests across multiple channels (UI, API, etc.) with automatic Cartesian product generation, test isolation markers, and time-dependent test support.
✅ Channel-Based Testing - Run tests across multiple channels (UI, API, etc.)
✅ Cartesian Product Generation - Automatically combine channels with test data
✅ Flexible Data Sources - Supports inline data, class data, and member data
✅ Test Isolation - Mark tests that require isolation ([Isolated])
✅ Time-Dependent Tests - Mark time-sensitive tests ([Time])
✅ .NET 8+ - Modern .NET support
dotnet add package Optivem.TestingRun the same test across multiple channels:
[Theory]
[ChannelData("UI", "API")]
public void CreateOrder_ShouldSucceed(Channel channel)
{
// Arrange
var order = new Order { ProductId = "P1", Quantity = 1 };
// Act
var result = channel.Type == "UI"
? CreateOrderViaUI(order)
: CreateOrderViaAPI(order);
// Assert
result.Success.ShouldBeTrue();
}
// Generates 2 tests: CreateOrder_ShouldSucceed(UI), CreateOrder_ShouldSucceed(API)Combine channels with test data for comprehensive coverage:
[Theory]
[ChannelData("UI", "API")]
[ChannelInlineData("", "Country must not be empty")]
[ChannelInlineData(" ", "Country must not be empty")]
[ChannelInlineData("123", "Country must contain only letters")]
public void CreateOrder_InvalidCountry_ShouldFail(
Channel channel,
string country,
string expectedError)
{
// Test implementation validates error message
}
// Generates 6 tests: 2 channels × 3 data rowsUse a class to provide test data:
public class InvalidCountryData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] { "", "Country must not be empty" };
yield return new object[] { " ", "Country must not be empty" };
yield return new object[] { "123", "Country must contain only letters" };
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Theory]
[ChannelData("UI", "API")]
[ChannelClassData(typeof(InvalidCountryData))]
public void CreateOrder_InvalidCountry_ShouldFail(
Channel channel,
string country,
string expectedError)
{
// Test implementation
}Use a method or property to provide test data:
public static IEnumerable<object[]> GetInvalidCountries()
{
yield return new object[] { "", "Country must not be empty" };
yield return new object[] { " ", "Country must not be empty" };
yield return new object[] { "123", "Country must contain only letters" };
}
[Theory]
[ChannelData("UI", "API")]
[ChannelMemberData(nameof(GetInvalidCountries))]
public void CreateOrder_InvalidCountry_ShouldFail(
Channel channel,
string country,
string expectedError)
{
// Test implementation
}Mark tests that require isolation from other tests:
[Fact]
[Isolated("Deletes all orders from database")]
public void ClearAllOrders_ShouldDeleteAllRecords()
{
// This test modifies shared state
}Filter isolated tests:
# Run ONLY isolated tests
dotnet test --filter "Category=isolated"
# Run all EXCEPT isolated tests
dotnet test --filter "Category!=isolated"Mark tests that depend on specific times:
[Fact]
[Time("2024-01-15T17:30:00Z")]
public void DiscountRate_ShouldBe15Percent_WhenAfter5pm()
{
// Test implementation
}Filter time-dependent tests:
# Run ONLY time-dependent tests
dotnet test --filter "Category=time"
# Run all EXCEPT time-dependent tests
dotnet test --filter "Category!=time"- Channel Naming - Use consistent channel names across your test suite (e.g., "UI", "API", "CLI")
- Isolation - Always mark tests with side effects using
[Isolated] - Test Data - Use
ChannelClassDataorChannelMemberDatafor complex test data - Time Tests -
[Time]tests are automatically marked as[Isolated]
- Reduce Duplication - Write test logic once, run across all channels
- Better Coverage - Cartesian products ensure comprehensive testing
- Clear Intent - Attributes make test requirements explicit
- Type Safety -
Channeltype prevents string typos - xUnit Compatible - Works seamlessly with existing xUnit tests
- .NET 8.0 or higher
- xUnit 2.x
Contributions welcome! Please open an issue or submit a pull request.
Built by Optivem