Skip to content

Testing Query Strategies ​

Query strategies should be tested to ensure they produce correct results. This guide covers both unit testing and integration testing approaches.

Unit Testing with In-Memory Collections ​

Unit tests validate the query logic without requiring a database. They use in-memory collections to verify filtering, sorting, and projections:

csharp
public class GetProductsQueryStrategyTests
{
    [Fact]
    public void Execute_ShouldFilterInStockProducts()
    {
        // Arrange
        var products = new List<Product>
        {
            new() { Id = 1, Name = "Product A", AvailableQuantity = 10 },
            new() { Id = 2, Name = "Product B", AvailableQuantity = 0 },
            new() { Id = 3, Name = "Product C", AvailableQuantity = 5 }
        }.AsQueryable();

        var strategy = new GetProductsQueryStrategy();

        // Act
        var result = strategy.Execute(products).ToList();

        // Assert
        Assert.Equal(2, result.Count);
        Assert.Contains(result, p => p.Name == "Product A");
        Assert.Contains(result, p => p.Name == "Product C");
        Assert.DoesNotContain(result, p => p.Name == "Product B");
    }

    [Fact]
    public void Execute_ShouldOrderByName()
    {
        // Arrange
        var products = new List<Product>
        {
            new() { Id = 1, Name = "Zebra", AvailableQuantity = 10 },
            new() { Id = 2, Name = "Apple", AvailableQuantity = 5 },
            new() { Id = 3, Name = "Mango", AvailableQuantity = 3 }
        }.AsQueryable();

        var strategy = new GetProductsQueryStrategy();

        // Act
        var result = strategy.Execute(products).ToList();

        // Assert
        Assert.Equal("Apple", result[0].Name);
        Assert.Equal("Mango", result[1].Name);
        Assert.Equal("Zebra", result[2].Name);
    }
}

Integration Testing with Database ​

Integration tests verify that query strategies work correctly with an actual database provider:

csharp
public class ProductQueryStrategyIntegrationTests : IAsyncLifetime
{
    private readonly IDbQuerySessionFactory _sessionFactory;

    public ProductQueryStrategyIntegrationTests()
    {
        _sessionFactory = CreateTestSessionFactory();
    }

    public async Task InitializeAsync()
    {
        // Seed test data
        await using var session = _sessionFactory.Create();
        session.Add(new Product { Name = "Test Product", AvailableQuantity = 10, Price = 50.0m });
        await session.SaveChangesAsync();
    }

    [Fact]
    public async Task Query_WithPagination_ShouldReturnCorrectPage()
    {
        // Arrange
        await using var session = _sessionFactory.Create();
        var strategy = new GetProductsQueryStrategy();

        // Act
        var query = session.Query(strategy);
        var pagedResult = await query.ToPagedListAsync(pageNumber: 1, pageSize: 10);

        // Assert
        Assert.NotEmpty(pagedResult);
        Assert.True(pagedResult.Count <= 10);
        Assert.Equal(1, pagedResult.PageNumber);
    }

    public async Task DisposeAsync()
    {
        // Cleanup test database
    }
}

Testing Dynamic Query Strategies ​

When testing query strategies with parameters, verify all parameter combinations:

csharp
public class SearchProductsQueryStrategyTests
{
    [Theory]
    [InlineData("Apple", null, null, 1)]
    [InlineData(null, 10.0, null, 2)]
    [InlineData(null, null, 50.0, 2)]
    [InlineData("Product", 20.0, 100.0, 1)]
    public void Execute_WithDifferentFilters_ShouldReturnExpectedCount(
        string? nameFilter,
        decimal? minPrice,
        decimal? maxPrice,
        int expectedCount)
    {
        // Arrange
        var products = new List<Product>
        {
            new() { Id = 1, Name = "Apple", Price = 15.0m },
            new() { Id = 2, Name = "Product A", Price = 25.0m },
            new() { Id = 3, Name = "Product B", Price = 75.0m }
        }.AsQueryable();

        var strategy = new SearchProductsQueryStrategy(nameFilter, minPrice, maxPrice);

        // Act
        var result = strategy.Execute(products).ToList();

        // Assert
        Assert.Equal(expectedCount, result.Count);
    }

    [Fact]
    public void Constructor_WithInvalidPriceRange_ShouldThrowException()
    {
        // Act & Assert
        Assert.Throws<ArgumentException>(() =>
            new SearchProductsQueryStrategy(minPrice: 100.0m, maxPrice: 50.0m));
    }
}

Testing Value Type Queries ​

Test query strategies that return value types to ensure nullable semantics work correctly:

csharp
public class AggregationQueryStrategyTests
{
    [Fact]
    public async Task QueryValue_WithResults_ShouldReturnValue()
    {
        // Arrange
        await using var session = _sessionFactory.Create();
        var strategy = ProductQueryStrategy.GetAveragePrice();

        // Act
        var query = session.QueryValue(strategy);
        var result = await query.FirstOrDefaultAsync();

        // Assert
        Assert.NotNull(result);
        Assert.True(result.Value > 0);
    }

    [Fact]
    public async Task QueryValue_WithNoResults_ShouldReturnNull()
    {
        // Arrange
        await using var session = _sessionFactory.Create();
        // Ensure no products exist
        var strategy = ProductQueryStrategy.GetAveragePrice();

        // Act
        var query = session.QueryValue(strategy);
        var result = await query.FirstOrDefaultAsync();

        // Assert
        Assert.Null(result);
    }
}

Best Practices for Testing ​

1. Test the Query Logic, Not the Framework ​

Focus on testing your business logic (filters, sorting, projections) rather than testing LINQ or Entity Framework functionality.

2. Use Realistic Test Data ​

Create test data that represents real-world scenarios, including edge cases:

csharp
private static IQueryable<Product> CreateTestProducts()
{
    return new List<Product>
    {
        // Normal cases
        new() { Id = 1, Name = "Product A", Price = 50.0m, AvailableQuantity = 10 },

        // Edge cases
        new() { Id = 2, Name = "", Price = 0.0m, AvailableQuantity = 0 },  // Empty values
        new() { Id = 3, Name = "Product C", Price = decimal.MaxValue, AvailableQuantity = int.MaxValue },  // Max values

        // Boundary cases
        new() { Id = 4, Name = "Product D", Price = 0.01m, AvailableQuantity = 1 }  // Minimum valid values
    }.AsQueryable();
}

3. Separate Unit and Integration Tests ​

Keep unit tests fast and isolated, while integration tests verify actual database behavior:

csharp
// Unit test - fast, no database
[Trait("Category", "Unit")]
public class GetProductsQueryStrategy_UnitTests { }

// Integration test - slower, uses database
[Trait("Category", "Integration")]
public class GetProductsQueryStrategy_IntegrationTests { }

4. Test Error Conditions ​

Verify that query strategies handle invalid inputs gracefully:

csharp
[Fact]
public void Constructor_WithNegativePrice_ShouldThrowException()
{
    Assert.Throws<ArgumentException>(() =>
        new SearchProductsQueryStrategy(minPrice: -10.0m));
}

Released under the MIT License.