Skip to content

Commit 1455ff7

Browse files
authored
Merge pull request #84 from Catlandor/73_ExpressionVisitor
82 / 73: Update NuGet packages, support for custom ExpressionVisitor
2 parents 7f02ca9 + 4af9128 commit 1455ff7

17 files changed

+948
-679
lines changed

src/MockQueryable/MockQueryable.Core/TestQueryProvider.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
namespace MockQueryable.Core
88
{
9-
public abstract class TestQueryProvider<T> : IOrderedQueryable<T>, IQueryProvider
10-
{
9+
public abstract class TestQueryProvider<T, TExpressionVisitor> : IOrderedQueryable<T>, IQueryProvider
10+
where TExpressionVisitor : ExpressionVisitor, new()
11+
{
1112
private IEnumerable<T> _enumerable;
1213

1314
protected TestQueryProvider(Expression expression)
@@ -40,7 +41,7 @@ public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)
4041

4142
private object CreateInstance(Type tElement, Expression expression)
4243
{
43-
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);
44+
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement, typeof(TExpressionVisitor));
4445
return Activator.CreateInstance(queryType, expression);
4546
}
4647

@@ -74,7 +75,7 @@ IEnumerator IEnumerable.GetEnumerator()
7475

7576
private static TResult CompileExpressionItem<TResult>(Expression expression)
7677
{
77-
var visitor = new TestExpressionVisitor();
78+
var visitor = new TExpressionVisitor();
7879
var body = visitor.Visit(expression);
7980
var f = Expression.Lambda<Func<TResult>>(body ?? throw new InvalidOperationException($"{nameof(body)} is null"), (IEnumerable<ParameterExpression>) null);
8081
return f.Compile()();

src/MockQueryable/MockQueryable.EntityFrameworkCore/MockQueryable.EntityFrameworkCore.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6</TargetFramework>
4+
<TargetFramework>net8</TargetFramework>
55
<PackageId>MockQueryable.EntityFrameworkCore</PackageId>
66
<Authors>Roman Titov</Authors>
77
<Description>
@@ -45,7 +45,7 @@
4545
</None>
4646
</ItemGroup>
4747
<ItemGroup>
48-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
48+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
4949
</ItemGroup>
5050

5151
<ItemGroup>
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
using MockQueryable.EntityFrameworkCore;
1+
using MockQueryable.Core;
2+
using MockQueryable.EntityFrameworkCore;
23
using System.Collections.Generic;
34
using System.Linq;
5+
using System.Linq.Expressions;
46

57
// Moving MockQueryableExtensions BuildMock into the MockQueryable.EntityFrameworkCore
68
// namespace had breaking changes with earlier extensions added to MockQueryable.Moq
@@ -9,11 +11,19 @@
911
// is dependent on the EF Core AsyncEnumerable.
1012
namespace MockQueryable
1113
{
12-
public static class MockQueryableExtensions
14+
public static class MockQueryableExtensions
15+
{
16+
public static IQueryable<TEntity> BuildMock<TEntity>(this IEnumerable<TEntity> data)
17+
where TEntity : class
1318
{
14-
public static IQueryable<TEntity> BuildMock<TEntity>(this IEnumerable<TEntity> data) where TEntity : class
15-
{
16-
return new TestAsyncEnumerableEfCore<TEntity>(data);
17-
}
19+
return new TestAsyncEnumerableEfCore<TEntity, TestExpressionVisitor>(data);
1820
}
21+
22+
public static IQueryable<TEntity> BuildMock<TEntity, TExpressionVisitor>(this IEnumerable<TEntity> data)
23+
where TEntity : class
24+
where TExpressionVisitor : ExpressionVisitor, new()
25+
{
26+
return new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data);
27+
}
28+
}
1929
}

src/MockQueryable/MockQueryable.EntityFrameworkCore/TestQueryProviderEfCore.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
namespace MockQueryable.EntityFrameworkCore
1010
{
11-
public class TestAsyncEnumerableEfCore<T>: TestQueryProvider<T>, IAsyncEnumerable<T>, IAsyncQueryProvider
11+
public class TestAsyncEnumerableEfCore<T, TExpressionVisitor>: TestQueryProvider<T, TExpressionVisitor>, IAsyncEnumerable<T>, IAsyncQueryProvider
12+
where TExpressionVisitor : ExpressionVisitor, new()
1213
{
1314
public TestAsyncEnumerableEfCore(Expression expression) : base(expression)
1415
{
@@ -25,11 +26,11 @@ public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken ca
2526
.GetMethods()
2627
.First(method => method.Name == nameof(IQueryProvider.Execute) && method.IsGenericMethod)
2728
.MakeGenericMethod(expectedResultType)
28-
.Invoke(this, new object[] { expression });
29+
.Invoke(this, [expression]);
2930

3031
return (TResult)typeof(Task).GetMethod(nameof(Task.FromResult))
3132
.MakeGenericMethod(expectedResultType)
32-
.Invoke(null, new[] { executionResult });
33+
.Invoke(null, [executionResult]);
3334
}
3435

3536
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
Lines changed: 85 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,105 @@
11
using FakeItEasy;
22
using Microsoft.EntityFrameworkCore;
3+
using MockQueryable.Core;
34
using MockQueryable.EntityFrameworkCore;
45
using System.Collections.Generic;
56
using System.Linq;
7+
using System.Linq.Expressions;
68
using System.Threading;
79
using System.Threading.Tasks;
810

911
namespace MockQueryable.FakeItEasy
1012
{
11-
public static class FakeItEasyExtensions
13+
public static class FakeItEasyExtensions
14+
{
15+
public static DbSet<TEntity> BuildMockDbSet<TEntity, TExpressionVisitor>(this IEnumerable<TEntity> data)
16+
where TEntity : class
17+
where TExpressionVisitor : ExpressionVisitor, new()
18+
=> data.BuildMock<TEntity, TExpressionVisitor>().BuildMockDbSet();
19+
20+
public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data)
21+
where TEntity : class
22+
=> data.BuildMock().BuildMockDbSet();
23+
24+
/// <summary>
25+
/// This method allows you to create a mock DbSet for testing purposes.
26+
/// It is particularly useful when you want to simulate the behavior of Entity Framework Core's DbSet
27+
/// with custom expression handling, such as for testing LINQ queries or database operations.
28+
/// The method takes an IQueryable of the entity type and returns a mocked DbSet that implements
29+
/// both IAsyncEnumerable and IQueryable interfaces, allowing for asynchronous enumeration
30+
/// and LINQ query capabilities.
31+
/// </summary>
32+
/// <typeparam name="TEntity">
33+
/// The type of the entity that the DbSet will represent.
34+
/// </typeparam>
35+
public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
1236
{
13-
public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data) where TEntity : class => data.BuildMock().BuildMockDbSet();
37+
return BuildMockDbSet<TEntity, TestExpressionVisitor>(data);
38+
}
1439

15-
public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
16-
{
17-
var mock = A.Fake<DbSet<TEntity>>(d => d.Implements<IAsyncEnumerable<TEntity>>().Implements<IQueryable<TEntity>>());
18-
var enumerable = new TestAsyncEnumerableEfCore<TEntity>(data);
19-
mock.ConfigureQueryableCalls(enumerable, data);
20-
mock.ConfigureAsyncEnumerableCalls(enumerable);
21-
mock.ConfigureDbSetCalls(data);
22-
if (mock is IAsyncEnumerable<TEntity> asyncEnumerable)
23-
{
24-
A.CallTo(() => asyncEnumerable.GetAsyncEnumerator(A<CancellationToken>.Ignored)).ReturnsLazily(() => enumerable.GetAsyncEnumerator());
25-
}
26-
return mock;
27-
}
40+
/// <summary>
41+
/// See <see cref="BuildMockDbSet{TEntity}"/>.
42+
/// </summary>
43+
/// <typeparam name="TEntity">
44+
/// The type of the entity that the DbSet will represent.
45+
/// </typeparam>
46+
/// <typeparam name="TExpressionVisitor">
47+
/// The type of the expression visitor that will be used to process LINQ expressions.
48+
/// Can be used to mock EF Core specific expression handling, such as for ILike expressions.
49+
/// </typeparam>
50+
public static DbSet<TEntity> BuildMockDbSet<TEntity, TExpressionVisitor>(this IQueryable<TEntity> data)
51+
where TEntity : class
52+
where TExpressionVisitor : ExpressionVisitor, new()
53+
{
54+
var mock = A.Fake<DbSet<TEntity>>(d => d.Implements<IAsyncEnumerable<TEntity>>().Implements<IQueryable<TEntity>>());
55+
var enumerable = new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data);
56+
mock.ConfigureQueryableCalls(enumerable, data);
57+
mock.ConfigureAsyncEnumerableCalls(enumerable);
58+
mock.ConfigureDbSetCalls(data);
2859

60+
if (mock is IAsyncEnumerable<TEntity> asyncEnumerable)
61+
{
62+
A.CallTo(() => asyncEnumerable.GetAsyncEnumerator(A<CancellationToken>.Ignored)).ReturnsLazily(() => enumerable.GetAsyncEnumerator());
63+
}
2964

30-
private static void ConfigureQueryableCalls<TEntity>(
31-
this IQueryable<TEntity> mock,
32-
IQueryProvider queryProvider,
33-
IQueryable<TEntity> data) where TEntity : class
34-
{
35-
A.CallTo(() => mock.Provider).Returns(queryProvider);
36-
A.CallTo(() => mock.Expression).Returns(data?.Expression);
37-
A.CallTo(() => mock.ElementType).Returns(data?.ElementType);
38-
A.CallTo(() => mock.GetEnumerator()).ReturnsLazily(() => data?.GetEnumerator());
39-
}
65+
return mock;
66+
}
4067

41-
private static void ConfigureAsyncEnumerableCalls<TEntity>(
42-
this DbSet<TEntity> mock,
43-
IAsyncEnumerable<TEntity> enumerable) where TEntity : class
44-
{
45-
A.CallTo(() => mock.GetAsyncEnumerator(A<CancellationToken>.Ignored))
46-
.Returns(enumerable.GetAsyncEnumerator());
47-
48-
}
68+
private static void ConfigureQueryableCalls<TEntity>(
69+
this IQueryable<TEntity> mock,
70+
IQueryProvider queryProvider,
71+
IQueryable<TEntity> data) where TEntity : class
72+
{
73+
A.CallTo(() => mock.Provider).Returns(queryProvider);
74+
A.CallTo(() => mock.Expression).Returns(data?.Expression);
75+
A.CallTo(() => mock.ElementType).Returns(data?.ElementType);
76+
A.CallTo(() => mock.GetEnumerator()).ReturnsLazily(() => data?.GetEnumerator());
77+
}
4978

50-
private static void ConfigureDbSetCalls<TEntity>(this DbSet<TEntity> mock, IQueryable<TEntity> data)
51-
where TEntity : class
52-
{
53-
A.CallTo(() => mock.AsQueryable()).Returns(data);
54-
A.CallTo(() => mock.AsAsyncEnumerable()).ReturnsLazily(args => CreateAsyncMock(data));
55-
}
79+
private static void ConfigureAsyncEnumerableCalls<TEntity>(
80+
this DbSet<TEntity> mock,
81+
IAsyncEnumerable<TEntity> enumerable) where TEntity : class
82+
{
83+
A.CallTo(() => mock.GetAsyncEnumerator(A<CancellationToken>.Ignored))
84+
.Returns(enumerable.GetAsyncEnumerator());
85+
}
86+
87+
private static void ConfigureDbSetCalls<TEntity>(this DbSet<TEntity> mock, IQueryable<TEntity> data)
88+
where TEntity : class
89+
{
90+
A.CallTo(() => mock.AsQueryable()).Returns(data);
91+
A.CallTo(() => mock.AsAsyncEnumerable()).ReturnsLazily(args => CreateAsyncMock(data));
92+
}
5693

57-
private static async IAsyncEnumerable<TEntity> CreateAsyncMock<TEntity>(IEnumerable<TEntity> data)
58-
where TEntity : class
59-
{
60-
foreach (var entity in data)
61-
{
62-
yield return entity;
63-
}
94+
private static async IAsyncEnumerable<TEntity> CreateAsyncMock<TEntity>(IEnumerable<TEntity> data)
95+
where TEntity : class
96+
{
97+
foreach (var entity in data)
98+
{
99+
yield return entity;
100+
}
64101

65-
await Task.CompletedTask;
66-
}
102+
await Task.CompletedTask;
67103
}
104+
}
68105
}

src/MockQueryable/MockQueryable.FakeItEasy/MockQueryable.FakeItEasy.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6</TargetFramework>
4+
<TargetFramework>net8</TargetFramework>
55
<PackageId>MockQueryable.FakeItEasy</PackageId>
66
<Authors>Roman Titov</Authors>
77
<Description>
@@ -26,7 +26,7 @@
2626
</PropertyGroup>
2727

2828
<ItemGroup>
29-
<PackageReference Include="FakeItEasy" Version="5.1.1" />
29+
<PackageReference Include="FakeItEasy" Version="8.3.0" />
3030
</ItemGroup>
3131

3232
<ItemGroup>

src/MockQueryable/MockQueryable.Moq/MockQueryable.Moq.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net6</TargetFramework>
4+
<TargetFramework>net8</TargetFramework>
55
<PackageId>MockQueryable.Moq</PackageId>
66
<Authors>Roman Titov</Authors>
77
<Description>
@@ -26,7 +26,7 @@
2626
</PropertyGroup>
2727

2828
<ItemGroup>
29-
<PackageReference Include="Moq" Version="4.8.0" />
29+
<PackageReference Include="Moq" Version="4.20.72" />
3030
</ItemGroup>
3131

3232

src/MockQueryable/MockQueryable.Moq/MoqExtensions.cs

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,69 @@
1-
using System.Collections.Generic;
1+
using Microsoft.EntityFrameworkCore;
2+
using MockQueryable.Core;
3+
using MockQueryable.EntityFrameworkCore;
4+
using Moq;
5+
using System.Collections.Generic;
26
using System.Linq;
7+
using System.Linq.Expressions;
38
using System.Threading;
49
using System.Threading.Tasks;
5-
using Microsoft.EntityFrameworkCore;
6-
using MockQueryable.EntityFrameworkCore;
7-
using Moq;
810

911
namespace MockQueryable.Moq
1012
{
1113
public static class MoqExtensions
1214
{
1315

14-
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data) where TEntity : class => data.BuildMock().BuildMockDbSet();
16+
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity, TExpressionVisitor>(this IEnumerable<TEntity> data)
17+
where TEntity : class
18+
where TExpressionVisitor : ExpressionVisitor, new()
19+
=> data.BuildMock<TEntity, TExpressionVisitor>().BuildMockDbSet();
1520

16-
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
21+
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data)
22+
where TEntity : class
23+
=> data.BuildMock().BuildMockDbSet();
24+
25+
/// <summary>
26+
/// This method allows you to create a mock DbSet for testing purposes.
27+
/// It is particularly useful when you want to simulate the behavior of Entity Framework Core's DbSet
28+
/// with custom expression handling, such as for testing LINQ queries or database operations.
29+
/// The method takes an IQueryable of the entity type and returns a mocked DbSet that implements
30+
/// both IAsyncEnumerable and IQueryable interfaces, allowing for asynchronous enumeration
31+
/// and LINQ query capabilities.
32+
/// </summary>
33+
/// <typeparam name="TEntity">
34+
/// The type of the entity that the DbSet will represent.
35+
/// </typeparam>
36+
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
1737
{
18-
var mock = new Mock<DbSet<TEntity>>();
19-
var enumerable = new TestAsyncEnumerableEfCore<TEntity>(data);
20-
mock.ConfigureAsyncEnumerableCalls(enumerable);
21-
mock.As<IQueryable<TEntity>>().ConfigureQueryableCalls(enumerable, data);
22-
mock.As<IAsyncEnumerable<TEntity>>().Setup(x => x.GetAsyncEnumerator(It.IsAny<CancellationToken>())).Returns(() => enumerable.GetAsyncEnumerator());
23-
mock.Setup(m => m.AsQueryable()).Returns(enumerable);
38+
return BuildMockDbSet<TEntity, TestExpressionVisitor>(data);
39+
}
2440

25-
mock.ConfigureDbSetCalls(data);
26-
return mock;
27-
}
41+
/// <summary>
42+
/// See <see cref="BuildMockDbSet{TEntity}"/>.
43+
/// </summary>
44+
/// <typeparam name="TEntity">
45+
/// The type of the entity that the DbSet will represent.
46+
/// </typeparam>
47+
/// <typeparam name="TExpressionVisitor">
48+
/// The type of the expression visitor that will be used to process LINQ expressions.
49+
/// Can be used to mock EF Core specific expression handling, such as for ILike expressions.
50+
/// </typeparam>
51+
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity, TExpressionVisitor>(this IQueryable<TEntity> data)
52+
where TEntity : class
53+
where TExpressionVisitor : ExpressionVisitor, new()
54+
{
55+
var mock = new Mock<DbSet<TEntity>>();
56+
var enumerable = new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data);
57+
mock.ConfigureAsyncEnumerableCalls(enumerable);
58+
mock.As<IQueryable<TEntity>>().ConfigureQueryableCalls(enumerable, data);
59+
mock.As<IAsyncEnumerable<TEntity>>().Setup(x => x.GetAsyncEnumerator(It.IsAny<CancellationToken>())).Returns(() => enumerable.GetAsyncEnumerator());
60+
mock.Setup(m => m.AsQueryable()).Returns(enumerable);
61+
62+
mock.ConfigureDbSetCalls(data);
63+
return mock;
64+
}
2865

29-
private static void ConfigureDbSetCalls<TEntity>(this Mock<DbSet<TEntity>> mock, IQueryable<TEntity> data)
66+
private static void ConfigureDbSetCalls<TEntity>(this Mock<DbSet<TEntity>> mock, IQueryable<TEntity> data)
3067
where TEntity : class
3168
{
3269
mock.Setup(m => m.AsQueryable()).Returns(mock.Object);

0 commit comments

Comments
 (0)