Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Tests.FixtureNew;

namespace Tests.Evaluators;

[Collection("SharedCollection")]
public class AsNoTrackingEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
{
private static readonly AsNoTrackingEvaluator _evaluator = AsNoTrackingEvaluator.Instance;

[Fact]
public void Applies_GivenAsNoTracking()
{
var spec = new Specification<Country>();
spec.Query.AsNoTracking();

var actual = _evaluator.GetQuery(DbContext.Countries, spec)
.Expression
.ToString();

var expected = DbContext.Countries
.AsNoTracking()
.AsQueryable().Expression
.ToString();

actual.Should().Be(expected);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Data.Entity;
using Tests.FixtureNew;

namespace Tests.Evaluators;

[Collection("SharedCollection")]
public class IncludeEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
{
private static readonly IncludeEvaluator _evaluator = IncludeEvaluator.Instance;

[Fact]
public void QueriesMatch_GivenNoIncludeExpression()
{
var spec = new Specification<Company>();

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies.AsQueryable();
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

[Fact]
public void QueriesMatch_GivenNoIncludeExpression_WithAutoOneToOne()
{
var spec = new Specification<Store>();

var actual = _evaluator.GetQuery(DbContext.Stores, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Stores.AsQueryable();
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

[Fact]
public void QueriesMatch_GivenSingleIncludeExpression_WithReferenceNavigation()
{
var spec = new Specification<Company>();
spec.Query
.Include(x => x.Country);

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies
.Include(x => x.Country);
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

[Fact]
public void QueriesMatch_GivenSingleIncludeExpression_WithCollectionNavigation()
{
var spec = new Specification<Company>();
spec.Query
.Include(x => x.Stores);

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies
.Include(x => x.Stores);
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

[Fact]
public void QueriesMatch_GivenMultipleIncludeExpression()
{
var spec = new Specification<Company>();
spec.Query
.Include(x => x.Country)
.Include(x => x.Stores);

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies
.Include(x => x.Country)
.Include(x => x.Stores);
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

// TODO: Found out that EF6 include evaluator fails for multiple include chains. [Fati Iseni, 15/06/2025]
//[Fact]
//public void QueriesMatch_GivenThenIncludeExpression()
//{
// var spec = new Specification<Store>();
// spec.Query
// .Include(x => x.Products)
// .ThenInclude(x => x.Images)
// .Include(x => x.Company)
// .ThenInclude(x => x.Country);

// var actual = _evaluator.GetQuery(DbContext.Stores, spec);
// var actualSql = GetQueryString(DbContext, actual);

// // EF6 doe't support ThenInclude, it uses string-based includes
// var expected = DbContext.Stores
// .Include($"{nameof(Store.Products)}.{nameof(Product.Images)}")
// .Include($"{nameof(Store.Company)}.{nameof(Company.Country)}");
// var expectedSql = GetQueryString(DbContext, expected);

// actualSql.Should().Be(expectedSql);
//}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Tests.FixtureNew;

namespace Tests.Evaluators;

[Collection("SharedCollection")]
public class OrderEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
{
private static readonly Ardalis.Specification.EntityFramework6.OrderEvaluator _evaluator =
Ardalis.Specification.EntityFramework6.OrderEvaluator.Instance;

[Fact]
public void QueriesMatch_GivenOrder()
{
var spec = new Specification<Company>();
spec.Query
.OrderBy(x => x.Id);

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies
.OrderBy(x => x.Id);
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

[Fact]
public void QueriesMatch_GivenOrderChain()
{
var spec = new Specification<Company>();
spec.Query
.OrderBy(x => x.Id)
.ThenBy(x => x.Name);

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies
.OrderBy(x => x.Id)
.ThenBy(x => x.Name);
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Data.Entity;
using Tests.FixtureNew;

namespace Tests.Evaluators;

[Collection("SharedCollection")]
public class SearchEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
{
private static readonly SearchEvaluator _evaluator = SearchEvaluator.Instance;

[Fact]
public void QueriesMatch_GivenNoSearch()
{
var spec = new Specification<Company>();
spec.Query
.Where(x => x.Id > 0);

var actual = _evaluator.GetQuery(DbContext.Companies, spec);
var actualSql = GetQueryString(DbContext, actual);

var expected = DbContext.Companies.AsQueryable();
var expectedSql = GetQueryString(DbContext, expected);

actualSql.Should().Be(expectedSql);
}

//// TODO: Not producing the same query, it's not parameterized and no null check [Fati Iseni, 15/06/2025]
//[Fact]
//public void QueriesMatch_GivenSingleSearch()
//{
// var storeTerm = "ab1";

// var spec = new Specification<Company>();
// spec.Query
// .Where(x => x.Id > 0)
// .Search(x => x.Name, $"%{storeTerm}%");

// var actual = _evaluator.GetQuery(DbContext.Companies, spec);
// var actualSql = GetQueryString(DbContext, actual);

// var expected = DbContext.Companies
// .Where(x => DbFunctions.Like(x.Name, "%" + storeTerm + "%"));
// var expectedSql = GetQueryString(DbContext, expected);

// actualSql.Should().Be(expectedSql);
//}

//// TODO: Not producing the same query, it's not parameterized and no null check [Fati Iseni, 15/06/2025]
//[Fact]
//public void QueriesMatch_GivenMultipleSearch()
//{
// var storeTerm = "ab1";
// var companyTerm = "ab2";
// var countryTerm = "ab3";
// var streetTerm = "ab4";

// var spec = new Specification<Store>();
// spec.Query
// .Where(x => x.Id > 0)
// .Search(x => x.Name, $"%{storeTerm}%")
// .Search(x => x.Company.Name, $"%{companyTerm}%")
// .Search(x => x.Company.Country.Name, $"%{countryTerm}%", 3)
// .Search(x => x.Address.Street, $"%{streetTerm}%", 2);

// var actual = _evaluator.GetQuery(DbContext.Stores, spec);
// var actualSql = GetQueryString(DbContext, actual);

// var expected = DbContext.Stores
// .Where(x => DbFunctions.Like(x.Name, "%" + storeTerm + "%")
// || DbFunctions.Like(x.Company.Name, "%" + storeTerm + "%"))
// .Where(x => DbFunctions.Like(x.Address.Street, "%" + storeTerm + "%"))
// .Where(x => DbFunctions.Like(x.Company.Country.Name, "%" + storeTerm + "%"));
// var expectedSql = GetQueryString(DbContext, expected);

// actualSql.Should().Be(expectedSql);
//}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace Tests.FixtureNew;
namespace Tests.FixtureNew;

public record Address
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema;

namespace Tests.FixtureNew;
namespace Tests.FixtureNew;

public record Company
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.IO;

namespace Tests.FixtureNew;

Expand All @@ -12,9 +13,30 @@ public IntegrationTest(TestFactory testFactory)
_testFactory = testFactory;
}

public static string GetQueryString<T>(TestDbContext dbContext, IQueryable<T> queryable)
{
// The EF6 doesn't support ToQueryString, so we need to log the SQL manually
var writer = new StringWriter();
dbContext.Database.Log = writer.Write;
_ = queryable.ToList(); // Execute the query to log the SQL
var sql = writer.ToString();

// Remove metadata lines (connection open/close, timestamps, execution comments)
var filteredLines = sql.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries)
.Where(line =>
!line.StartsWith("Opened connection") &&
!line.StartsWith("Closed connection") &&
!line.StartsWith("-- Executing") &&
!line.StartsWith("-- Completed")
);
return string.Join(Environment.NewLine, filteredLines).Trim();
}

public Task InitializeAsync()
{
DbContext = new TestDbContext(_testFactory.ConnectionString);
// On first access, there are additional queries and is skewing our tests.
_ = DbContext.Countries.Any();
return Task.CompletedTask;
}

Expand Down