Skip to content

Commit 48882d7

Browse files
authored
Add tests for EF6 evaluators. (#520)
* Add AsNoTrackingEvaluator tests. * Add IncludeEvaluator tests. * Add OrderEvaluator tests * Add SearchEvaluator tests. * Add Skip attributes instead of commented tests.
1 parent 79d332d commit 48882d7

File tree

7 files changed

+286
-6
lines changed

7 files changed

+286
-6
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Tests.FixtureNew;
2+
3+
namespace Tests.Evaluators;
4+
5+
[Collection("SharedCollection")]
6+
public class AsNoTrackingEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
7+
{
8+
private static readonly AsNoTrackingEvaluator _evaluator = AsNoTrackingEvaluator.Instance;
9+
10+
[Fact]
11+
public void Applies_GivenAsNoTracking()
12+
{
13+
var spec = new Specification<Country>();
14+
spec.Query.AsNoTracking();
15+
16+
var actual = _evaluator.GetQuery(DbContext.Countries, spec)
17+
.Expression
18+
.ToString();
19+
20+
var expected = DbContext.Countries
21+
.AsNoTracking()
22+
.AsQueryable().Expression
23+
.ToString();
24+
25+
actual.Should().Be(expected);
26+
}
27+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using System.Data.Entity;
2+
using Tests.FixtureNew;
3+
4+
namespace Tests.Evaluators;
5+
6+
[Collection("SharedCollection")]
7+
public class IncludeEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
8+
{
9+
private static readonly IncludeEvaluator _evaluator = IncludeEvaluator.Instance;
10+
11+
[Fact]
12+
public void QueriesMatch_GivenNoIncludeExpression()
13+
{
14+
var spec = new Specification<Company>();
15+
16+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
17+
var actualSql = GetQueryString(DbContext, actual);
18+
19+
var expected = DbContext.Companies.AsQueryable();
20+
var expectedSql = GetQueryString(DbContext, expected);
21+
22+
actualSql.Should().Be(expectedSql);
23+
}
24+
25+
[Fact]
26+
public void QueriesMatch_GivenNoIncludeExpression_WithAutoOneToOne()
27+
{
28+
var spec = new Specification<Store>();
29+
30+
var actual = _evaluator.GetQuery(DbContext.Stores, spec);
31+
var actualSql = GetQueryString(DbContext, actual);
32+
33+
var expected = DbContext.Stores.AsQueryable();
34+
var expectedSql = GetQueryString(DbContext, expected);
35+
36+
actualSql.Should().Be(expectedSql);
37+
}
38+
39+
[Fact]
40+
public void QueriesMatch_GivenSingleIncludeExpression_WithReferenceNavigation()
41+
{
42+
var spec = new Specification<Company>();
43+
spec.Query
44+
.Include(x => x.Country);
45+
46+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
47+
var actualSql = GetQueryString(DbContext, actual);
48+
49+
var expected = DbContext.Companies
50+
.Include(x => x.Country);
51+
var expectedSql = GetQueryString(DbContext, expected);
52+
53+
actualSql.Should().Be(expectedSql);
54+
}
55+
56+
[Fact]
57+
public void QueriesMatch_GivenSingleIncludeExpression_WithCollectionNavigation()
58+
{
59+
var spec = new Specification<Company>();
60+
spec.Query
61+
.Include(x => x.Stores);
62+
63+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
64+
var actualSql = GetQueryString(DbContext, actual);
65+
66+
var expected = DbContext.Companies
67+
.Include(x => x.Stores);
68+
var expectedSql = GetQueryString(DbContext, expected);
69+
70+
actualSql.Should().Be(expectedSql);
71+
}
72+
73+
[Fact]
74+
public void QueriesMatch_GivenMultipleIncludeExpression()
75+
{
76+
var spec = new Specification<Company>();
77+
spec.Query
78+
.Include(x => x.Country)
79+
.Include(x => x.Stores);
80+
81+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
82+
var actualSql = GetQueryString(DbContext, actual);
83+
84+
var expected = DbContext.Companies
85+
.Include(x => x.Country)
86+
.Include(x => x.Stores);
87+
var expectedSql = GetQueryString(DbContext, expected);
88+
89+
actualSql.Should().Be(expectedSql);
90+
}
91+
92+
93+
[Fact(Skip = "EF6 include evaluator fails for multiple include chains [Fati Iseni, 15/06/2025]")]
94+
public void QueriesMatch_GivenThenIncludeExpression()
95+
{
96+
var spec = new Specification<Store>();
97+
spec.Query
98+
.Include(x => x.Products)
99+
.ThenInclude(x => x.Images)
100+
.Include(x => x.Company)
101+
.ThenInclude(x => x.Country);
102+
103+
var actual = _evaluator.GetQuery(DbContext.Stores, spec);
104+
var actualSql = GetQueryString(DbContext, actual);
105+
106+
// EF6 doe't support ThenInclude, it uses string-based includes
107+
var expected = DbContext.Stores
108+
.Include($"{nameof(Store.Products)}.{nameof(Product.Images)}")
109+
.Include($"{nameof(Store.Company)}.{nameof(Company.Country)}");
110+
var expectedSql = GetQueryString(DbContext, expected);
111+
112+
actualSql.Should().Be(expectedSql);
113+
}
114+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using Tests.FixtureNew;
2+
3+
namespace Tests.Evaluators;
4+
5+
[Collection("SharedCollection")]
6+
public class OrderEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
7+
{
8+
private static readonly Ardalis.Specification.EntityFramework6.OrderEvaluator _evaluator =
9+
Ardalis.Specification.EntityFramework6.OrderEvaluator.Instance;
10+
11+
[Fact]
12+
public void QueriesMatch_GivenOrder()
13+
{
14+
var spec = new Specification<Company>();
15+
spec.Query
16+
.OrderBy(x => x.Id);
17+
18+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
19+
var actualSql = GetQueryString(DbContext, actual);
20+
21+
var expected = DbContext.Companies
22+
.OrderBy(x => x.Id);
23+
var expectedSql = GetQueryString(DbContext, expected);
24+
25+
actualSql.Should().Be(expectedSql);
26+
}
27+
28+
[Fact]
29+
public void QueriesMatch_GivenOrderChain()
30+
{
31+
var spec = new Specification<Company>();
32+
spec.Query
33+
.OrderBy(x => x.Id)
34+
.ThenBy(x => x.Name);
35+
36+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
37+
var actualSql = GetQueryString(DbContext, actual);
38+
39+
var expected = DbContext.Companies
40+
.OrderBy(x => x.Id)
41+
.ThenBy(x => x.Name);
42+
var expectedSql = GetQueryString(DbContext, expected);
43+
44+
actualSql.Should().Be(expectedSql);
45+
}
46+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Data.Entity;
2+
using Tests.FixtureNew;
3+
4+
namespace Tests.Evaluators;
5+
6+
[Collection("SharedCollection")]
7+
public class SearchEvaluatorTests(TestFactory factory) : IntegrationTest(factory)
8+
{
9+
private static readonly SearchEvaluator _evaluator = SearchEvaluator.Instance;
10+
11+
[Fact]
12+
public void QueriesMatch_GivenNoSearch()
13+
{
14+
var spec = new Specification<Company>();
15+
spec.Query
16+
.Where(x => x.Id > 0);
17+
18+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
19+
var actualSql = GetQueryString(DbContext, actual);
20+
21+
var expected = DbContext.Companies.AsQueryable();
22+
var expectedSql = GetQueryString(DbContext, expected);
23+
24+
actualSql.Should().Be(expectedSql);
25+
}
26+
27+
[Fact(Skip = "Not producing the same query, it's not parameterized and no null check [Fati Iseni, 15/06/2025]")]
28+
public void QueriesMatch_GivenSingleSearch()
29+
{
30+
var storeTerm = "ab1";
31+
32+
var spec = new Specification<Company>();
33+
spec.Query
34+
.Where(x => x.Id > 0)
35+
.Search(x => x.Name, $"%{storeTerm}%");
36+
37+
var actual = _evaluator.GetQuery(DbContext.Companies, spec);
38+
var actualSql = GetQueryString(DbContext, actual);
39+
40+
var expected = DbContext.Companies
41+
.Where(x => DbFunctions.Like(x.Name, "%" + storeTerm + "%"));
42+
var expectedSql = GetQueryString(DbContext, expected);
43+
44+
actualSql.Should().Be(expectedSql);
45+
}
46+
47+
[Fact(Skip = "Not producing the same query, it's not parameterized and no null check [Fati Iseni, 15/06/2025]")]
48+
public void QueriesMatch_GivenMultipleSearch()
49+
{
50+
var storeTerm = "ab1";
51+
var companyTerm = "ab2";
52+
var countryTerm = "ab3";
53+
var streetTerm = "ab4";
54+
55+
var spec = new Specification<Store>();
56+
spec.Query
57+
.Where(x => x.Id > 0)
58+
.Search(x => x.Name, $"%{storeTerm}%")
59+
.Search(x => x.Company.Name, $"%{companyTerm}%")
60+
.Search(x => x.Company.Country.Name, $"%{countryTerm}%", 3)
61+
.Search(x => x.Address.Street, $"%{streetTerm}%", 2);
62+
63+
var actual = _evaluator.GetQuery(DbContext.Stores, spec);
64+
var actualSql = GetQueryString(DbContext, actual);
65+
66+
var expected = DbContext.Stores
67+
.Where(x => DbFunctions.Like(x.Name, "%" + storeTerm + "%")
68+
|| DbFunctions.Like(x.Company.Name, "%" + storeTerm + "%"))
69+
.Where(x => DbFunctions.Like(x.Address.Street, "%" + storeTerm + "%"))
70+
.Where(x => DbFunctions.Like(x.Company.Country.Name, "%" + storeTerm + "%"));
71+
var expectedSql = GetQueryString(DbContext, expected);
72+
73+
actualSql.Should().Be(expectedSql);
74+
}
75+
}

tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Address.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.ComponentModel.DataAnnotations.Schema;
2-
3-
namespace Tests.FixtureNew;
1+
namespace Tests.FixtureNew;
42

53
public record Address
64
{

tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Company.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.ComponentModel.DataAnnotations.Schema;
2-
3-
namespace Tests.FixtureNew;
1+
namespace Tests.FixtureNew;
42

53
public record Company
64
{

tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/IntegrationTest.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections;
2+
using System.IO;
23

34
namespace Tests.FixtureNew;
45

@@ -12,9 +13,30 @@ public IntegrationTest(TestFactory testFactory)
1213
_testFactory = testFactory;
1314
}
1415

16+
public static string GetQueryString<T>(TestDbContext dbContext, IQueryable<T> queryable)
17+
{
18+
// The EF6 doesn't support ToQueryString, so we need to log the SQL manually
19+
var writer = new StringWriter();
20+
dbContext.Database.Log = writer.Write;
21+
_ = queryable.ToList(); // Execute the query to log the SQL
22+
var sql = writer.ToString();
23+
24+
// Remove metadata lines (connection open/close, timestamps, execution comments)
25+
var filteredLines = sql.Split([Environment.NewLine], StringSplitOptions.RemoveEmptyEntries)
26+
.Where(line =>
27+
!line.StartsWith("Opened connection") &&
28+
!line.StartsWith("Closed connection") &&
29+
!line.StartsWith("-- Executing") &&
30+
!line.StartsWith("-- Completed")
31+
);
32+
return string.Join(Environment.NewLine, filteredLines).Trim();
33+
}
34+
1535
public Task InitializeAsync()
1636
{
1737
DbContext = new TestDbContext(_testFactory.ConnectionString);
38+
// On first access, there are additional queries and is skewing our tests.
39+
_ = DbContext.Countries.Any();
1840
return Task.CompletedTask;
1941
}
2042

0 commit comments

Comments
 (0)