This guide explains how to run different types of tests in the XpressData project.
Tests are organized by feature area and follow standard xUnit conventions.
- Datalayer Tests - Tests for database connection, query, and parameter handling
- Expression Tests - Tests for SQL expression building and generation
- Mapping Tests - Tests for entity mapping, field types, and attributes
XpressData.Test/
├── Datalayer/
│ ├── QueryParameterTests.cs # Query parameter creation and handling
│ ├── QueryTests.cs # Query building and SQL text generation
│ └── DatabaseInformationTests.cs # Connection string and DB info parsing
├── Expressions/
│ ├── BuilderTests.cs # Expression builder API tests
│ └── ExpressionOutputTests.cs # SQL output generation tests
└── Mapping/
└── FieldTypeTests.cs # Field type SQL generation tests
dotnet testdotnet test --filter "FullyQualifiedName~XpressData.Test.Datalayer"dotnet test --filter "FullyQualifiedName~BuilderTests"dotnet test --filter "FullyQualifiedName~Equal_WithNumericExpressions_ReturnsExpressionEqual"dotnet test --verbosity normaldotnet test --collect:"XPlat Code Coverage"Tests for core database components:
dotnet test --filter "FullyQualifiedName~XpressData.Test.Datalayer"Key scenarios tested:
- QueryParameter creation with various types
- Query building with parameters
- Connection string parsing
- Database information validation
Tests for SQL expression building:
dotnet test --filter "FullyQualifiedName~XpressData.Test.Expressions"Key scenarios tested:
- Field expressions with/without aliases
- Value expressions for all data types
- Comparison operators (Equal, NotEqual, MoreThan, LessThan, etc.)
- Logical operators (And, Or, Not)
- Null checks (IsNull, IsNotNull)
- Collection expressions (In, NotIn)
- Pattern matching (Like)
- Arithmetic expressions (Add, Minus)
- Assignment expressions for UPDATE statements
- OrderBy chain building
Tests for entity and field mapping:
dotnet test --filter "FullyQualifiedName~XpressData.Test.Mapping"Key scenarios tested:
- Field type SQL generation
- Value conversion to/from database
- Null handling
When using the Builder API, explicit interface casting may be required for type safety:
// Numeric comparison - cast to IExpressionNumeric
IExpressionNumeric idField = Builder.Field(userMap.Id);
IExpressionNumeric value = Builder.Value(42);
var condition = Builder.Equal(idField, value);
// String comparison - cast to IExpressionString
IExpressionString nameField = Builder.Field(userMap.Name);
IExpressionString pattern = Builder.Value("%test%");
var likeCondition = Builder.Like(nameField, pattern);
// GUID comparison - cast to IExpressionGuid
IExpressionGuid guidField = Builder.Field(userMap.UniqueId);
IExpressionGuid guidValue = Builder.Value(Guid.NewGuid());
var guidCondition = Builder.Equal(guidField, guidValue);| Interface | Use Case |
|---|---|
IExpressionNumeric |
int, long, decimal comparisons |
IExpressionString |
String comparisons, LIKE |
IExpressionGuid |
GUID comparisons |
IExpressionDate |
Date-only comparisons |
IExpressionDateTime |
DateTime comparisons |
IExpressionLogical |
Boolean/logical operations |
IExpressionComperable |
IN/NOT IN operations |
IExpressionAny |
Null checks, assignments |
Follow the pattern: MethodName_Scenario_ExpectedResult
[Fact]
public void Equal_WithNumericExpressions_ReturnsExpressionEqual()
{
// Arrange - Cast to appropriate interface
IExpressionNumeric left = Builder.Field(_testIntField);
IExpressionNumeric right = Builder.Value(42);
// Act
var result = Builder.Equal(left, right);
// Assert
Assert.NotNull(result);
Assert.IsType<ExpressionEqual>(result);
}All test methods should have XML documentation:
/// <summary>
/// Verifies that Equal() with numeric expressions returns the correct type.
/// Note: Explicit cast to IExpressionNumeric is required.
/// </summary>
[Fact]
public void Equal_WithNumericExpressions_ReturnsExpressionEqual()Tests are automatically run on every pull request. Ensure all tests pass before merging.
dotnet test --filter "FullyQualifiedName~HandlerOrderingTests"Key scenarios tested:
- Handlers execute in Order property sequence
- Deterministic tie-breaking for same Order values
- Multiple handlers all execute
dotnet test --filter "FullyQualifiedName~ConcurrencyTests"Key scenarios tested:
- Multiple producers enqueueing concurrently
- Context isolation under concurrent load
- Thread-safe operations
Follow this pattern for new tests:
[Fact]
public async Task MethodName_Condition_ExpectedBehavior()
{
// Arrange
using var provider = TestServiceProviderBuilder.CreateWithXpressWork();
await TestServiceProviderBuilder.StartQueueAsync(provider);
try
{
// Act
using var scope = TestServiceProviderBuilder.CreateScopeWithContext(
provider,
TestContext.Create());
var submitter = scope.ServiceProvider
.GetRequiredService<IBackgroundWorkSubmitter<TestContext>>();
// ... perform action ...
// Assert
Assert.NotNull(result);
}
finally
{
await TestServiceProviderBuilder.StopQueueAsync(provider);
}
}For tests with custom configuration:
var provider = TestServiceProviderBuilder.CreateWithXpressWork(services =>
{
// Add additional test services
services.AddSingleton<IMyTestService, MyTestService>();
});For scheduler tests, also start the scheduler:
var scheduler = provider.GetRequiredService<IBackgroundWorkScheduler<TestContext>>();
await ((IHostedService)scheduler).StartAsync(CancellationToken.None);# Run all tests
dotnet test --verbosity minimalTypical Duration: 5-10 seconds
# Run all tests with coverage
dotnet test --collect:"XPlat Code Coverage" --results-directory ./coverage- Open Test Explorer (Test > Test Explorer)
- Build solution to discover tests
- Run all or select specific tests
- Use filter to find specific tests
- Install C# Dev Kit extension
- Open Testing view (beaker icon)
- Run tests from the tree view
- Open Unit Tests window (View > Tool Windows > Unit Tests)
- Build to discover tests
- Right-click to run specific tests
Symptom: Tests fail with timeout errors
Cause: Async operations not completing in time
Solution:
// Use explicit timeouts
await completion.Task.WaitAsync(TimeSpan.FromSeconds(10));Symptom: _contextAccessor.Context is null
Cause: Context not properly captured or rehydrated
Solution:
- Ensure scope is created with
CreateScopeWithContext - Verify scope factory is correctly implementing rehydration
- Check that context provider is registered
Symptom: Work handlers don't run
Cause: Handler not registered or queue not started
Solution:
- Verify handler is registered with
AddWorkHandler<TArgs, THandler>() - Ensure queue is started before enqueueing
- Check completion task for exceptions
Symptom: Scheduler tests occasionally fail
Cause: Timing-dependent tests sensitive to system load
Solution:
- Use longer intervals in tests (e.g., 100-200ms instead of 50ms)
- Add adequate delays after scheduling
- Use task completion signals instead of fixed delays where possible
- ✅ ...
- ✅ ...
- ✅ ...
- ...
- ...
- ...
- Always dispose service providers
- Use
try/finallyto ensure queue is stopped - Test both success and failure paths
- Test edge cases (null, empty, maximum values)
- Use meaningful test names describing behavior
- Don't share state between tests
- Don't use hard-coded delays for async operations
- Don't test implementation details, test behavior
- Don't ignore failed tests