Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 5 additions & 4 deletions src/MockQueryable/MockQueryable.Core/TestQueryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

namespace MockQueryable.Core
{
public abstract class TestQueryProvider<T> : IOrderedQueryable<T>, IQueryProvider
{
public abstract class TestQueryProvider<T, TExpressionVisitor> : IOrderedQueryable<T>, IQueryProvider
where TExpressionVisitor : ExpressionVisitor, new()
{
private IEnumerable<T> _enumerable;

protected TestQueryProvider(Expression expression)
Expand Down Expand Up @@ -40,7 +41,7 @@ public IQueryable<TEntity> CreateQuery<TEntity>(Expression expression)

private object CreateInstance(Type tElement, Expression expression)
{
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement);
var queryType = GetType().GetGenericTypeDefinition().MakeGenericType(tElement, typeof(TExpressionVisitor));
return Activator.CreateInstance(queryType, expression);
}

Expand Down Expand Up @@ -74,7 +75,7 @@ IEnumerator IEnumerable.GetEnumerator()

private static TResult CompileExpressionItem<TResult>(Expression expression)
{
var visitor = new TestExpressionVisitor();
var visitor = new TExpressionVisitor();
var body = visitor.Visit(expression);
var f = Expression.Lambda<Func<TResult>>(body ?? throw new InvalidOperationException($"{nameof(body)} is null"), (IEnumerable<ParameterExpression>) null);
return f.Compile()();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>net8</TargetFramework>
<PackageId>MockQueryable.EntityFrameworkCore</PackageId>
<Authors>Roman Titov</Authors>
<Description>
Expand Down Expand Up @@ -45,7 +45,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using MockQueryable.EntityFrameworkCore;
using MockQueryable.Core;
using MockQueryable.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

// Moving MockQueryableExtensions BuildMock into the MockQueryable.EntityFrameworkCore
// namespace had breaking changes with earlier extensions added to MockQueryable.Moq
Expand All @@ -9,11 +11,19 @@
// is dependent on the EF Core AsyncEnumerable.
namespace MockQueryable
{
public static class MockQueryableExtensions
public static class MockQueryableExtensions
{
public static IQueryable<TEntity> BuildMock<TEntity>(this IEnumerable<TEntity> data)
where TEntity : class
{
public static IQueryable<TEntity> BuildMock<TEntity>(this IEnumerable<TEntity> data) where TEntity : class
{
return new TestAsyncEnumerableEfCore<TEntity>(data);
}
return new TestAsyncEnumerableEfCore<TEntity, TestExpressionVisitor>(data);
}

public static IQueryable<TEntity> BuildMock<TEntity, TExpressionVisitor>(this IEnumerable<TEntity> data)
where TEntity : class
where TExpressionVisitor : ExpressionVisitor, new()
{
return new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

namespace MockQueryable.EntityFrameworkCore
{
public class TestAsyncEnumerableEfCore<T>: TestQueryProvider<T>, IAsyncEnumerable<T>, IAsyncQueryProvider
public class TestAsyncEnumerableEfCore<T, TExpressionVisitor>: TestQueryProvider<T, TExpressionVisitor>, IAsyncEnumerable<T>, IAsyncQueryProvider
where TExpressionVisitor : ExpressionVisitor, new()
{
public TestAsyncEnumerableEfCore(Expression expression) : base(expression)
{
Expand All @@ -25,11 +26,11 @@ public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken ca
.GetMethods()
.First(method => method.Name == nameof(IQueryProvider.Execute) && method.IsGenericMethod)
.MakeGenericMethod(expectedResultType)
.Invoke(this, new object[] { expression });
.Invoke(this, [expression]);

return (TResult)typeof(Task).GetMethod(nameof(Task.FromResult))
.MakeGenericMethod(expectedResultType)
.Invoke(null, new[] { executionResult });
.Invoke(null, [executionResult]);
}

public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
Expand Down
133 changes: 85 additions & 48 deletions src/MockQueryable/MockQueryable.FakeItEasy/FakeItEasyExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,105 @@
using FakeItEasy;
using Microsoft.EntityFrameworkCore;
using MockQueryable.Core;
using MockQueryable.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace MockQueryable.FakeItEasy
{
public static class FakeItEasyExtensions
public static class FakeItEasyExtensions
{
public static DbSet<TEntity> BuildMockDbSet<TEntity, TExpressionVisitor>(this IEnumerable<TEntity> data)
where TEntity : class
where TExpressionVisitor : ExpressionVisitor, new()
=> data.BuildMock<TEntity, TExpressionVisitor>().BuildMockDbSet();

public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data)
where TEntity : class
=> data.BuildMock().BuildMockDbSet();

/// <summary>
/// This method allows you to create a mock DbSet for testing purposes.
/// It is particularly useful when you want to simulate the behavior of Entity Framework Core's DbSet
/// with custom expression handling, such as for testing LINQ queries or database operations.
/// The method takes an IQueryable of the entity type and returns a mocked DbSet that implements
/// both IAsyncEnumerable and IQueryable interfaces, allowing for asynchronous enumeration
/// and LINQ query capabilities.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity that the DbSet will represent.
/// </typeparam>
public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
{
public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data) where TEntity : class => data.BuildMock().BuildMockDbSet();
return BuildMockDbSet<TEntity, TestExpressionVisitor>(data);
}

public static DbSet<TEntity> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
{
var mock = A.Fake<DbSet<TEntity>>(d => d.Implements<IAsyncEnumerable<TEntity>>().Implements<IQueryable<TEntity>>());
var enumerable = new TestAsyncEnumerableEfCore<TEntity>(data);
mock.ConfigureQueryableCalls(enumerable, data);
mock.ConfigureAsyncEnumerableCalls(enumerable);
mock.ConfigureDbSetCalls(data);
if (mock is IAsyncEnumerable<TEntity> asyncEnumerable)
{
A.CallTo(() => asyncEnumerable.GetAsyncEnumerator(A<CancellationToken>.Ignored)).ReturnsLazily(() => enumerable.GetAsyncEnumerator());
}
return mock;
}
/// <summary>
/// See <see cref="BuildMockDbSet{TEntity}"/>.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity that the DbSet will represent.
/// </typeparam>
/// <typeparam name="TExpressionVisitor">
/// The type of the expression visitor that will be used to process LINQ expressions.
/// Can be used to mock EF Core specific expression handling, such as for ILike expressions.
/// </typeparam>
public static DbSet<TEntity> BuildMockDbSet<TEntity, TExpressionVisitor>(this IQueryable<TEntity> data)
where TEntity : class
where TExpressionVisitor : ExpressionVisitor, new()
{
var mock = A.Fake<DbSet<TEntity>>(d => d.Implements<IAsyncEnumerable<TEntity>>().Implements<IQueryable<TEntity>>());
var enumerable = new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data);
mock.ConfigureQueryableCalls(enumerable, data);
mock.ConfigureAsyncEnumerableCalls(enumerable);
mock.ConfigureDbSetCalls(data);

if (mock is IAsyncEnumerable<TEntity> asyncEnumerable)
{
A.CallTo(() => asyncEnumerable.GetAsyncEnumerator(A<CancellationToken>.Ignored)).ReturnsLazily(() => enumerable.GetAsyncEnumerator());
}

private static void ConfigureQueryableCalls<TEntity>(
this IQueryable<TEntity> mock,
IQueryProvider queryProvider,
IQueryable<TEntity> data) where TEntity : class
{
A.CallTo(() => mock.Provider).Returns(queryProvider);
A.CallTo(() => mock.Expression).Returns(data?.Expression);
A.CallTo(() => mock.ElementType).Returns(data?.ElementType);
A.CallTo(() => mock.GetEnumerator()).ReturnsLazily(() => data?.GetEnumerator());
}
return mock;
}

private static void ConfigureAsyncEnumerableCalls<TEntity>(
this DbSet<TEntity> mock,
IAsyncEnumerable<TEntity> enumerable) where TEntity : class
{
A.CallTo(() => mock.GetAsyncEnumerator(A<CancellationToken>.Ignored))
.Returns(enumerable.GetAsyncEnumerator());

}
private static void ConfigureQueryableCalls<TEntity>(
this IQueryable<TEntity> mock,
IQueryProvider queryProvider,
IQueryable<TEntity> data) where TEntity : class
{
A.CallTo(() => mock.Provider).Returns(queryProvider);
A.CallTo(() => mock.Expression).Returns(data?.Expression);
A.CallTo(() => mock.ElementType).Returns(data?.ElementType);
A.CallTo(() => mock.GetEnumerator()).ReturnsLazily(() => data?.GetEnumerator());
}

private static void ConfigureDbSetCalls<TEntity>(this DbSet<TEntity> mock, IQueryable<TEntity> data)
where TEntity : class
{
A.CallTo(() => mock.AsQueryable()).Returns(data);
A.CallTo(() => mock.AsAsyncEnumerable()).ReturnsLazily(args => CreateAsyncMock(data));
}
private static void ConfigureAsyncEnumerableCalls<TEntity>(
this DbSet<TEntity> mock,
IAsyncEnumerable<TEntity> enumerable) where TEntity : class
{
A.CallTo(() => mock.GetAsyncEnumerator(A<CancellationToken>.Ignored))
.Returns(enumerable.GetAsyncEnumerator());
}

private static void ConfigureDbSetCalls<TEntity>(this DbSet<TEntity> mock, IQueryable<TEntity> data)
where TEntity : class
{
A.CallTo(() => mock.AsQueryable()).Returns(data);
A.CallTo(() => mock.AsAsyncEnumerable()).ReturnsLazily(args => CreateAsyncMock(data));
}

private static async IAsyncEnumerable<TEntity> CreateAsyncMock<TEntity>(IEnumerable<TEntity> data)
where TEntity : class
{
foreach (var entity in data)
{
yield return entity;
}
private static async IAsyncEnumerable<TEntity> CreateAsyncMock<TEntity>(IEnumerable<TEntity> data)
where TEntity : class
{
foreach (var entity in data)
{
yield return entity;
}

await Task.CompletedTask;
}
await Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>net8</TargetFramework>
<PackageId>MockQueryable.FakeItEasy</PackageId>
<Authors>Roman Titov</Authors>
<Description>
Expand All @@ -26,7 +26,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FakeItEasy" Version="5.1.1" />
<PackageReference Include="FakeItEasy" Version="8.3.0" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/MockQueryable/MockQueryable.Moq/MockQueryable.Moq.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<TargetFramework>net8</TargetFramework>
<PackageId>MockQueryable.Moq</PackageId>
<Authors>Roman Titov</Authors>
<Description>
Expand All @@ -26,7 +26,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Moq" Version="4.8.0" />
<PackageReference Include="Moq" Version="4.20.72" />
</ItemGroup>


Expand Down
69 changes: 53 additions & 16 deletions src/MockQueryable/MockQueryable.Moq/MoqExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,69 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using MockQueryable.Core;
using MockQueryable.EntityFrameworkCore;
using Moq;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using MockQueryable.EntityFrameworkCore;
using Moq;

namespace MockQueryable.Moq
{
public static class MoqExtensions
{

public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data) where TEntity : class => data.BuildMock().BuildMockDbSet();
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity, TExpressionVisitor>(this IEnumerable<TEntity> data)
where TEntity : class
where TExpressionVisitor : ExpressionVisitor, new()
=> data.BuildMock<TEntity, TExpressionVisitor>().BuildMockDbSet();

public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IEnumerable<TEntity> data)
where TEntity : class
=> data.BuildMock().BuildMockDbSet();

/// <summary>
/// This method allows you to create a mock DbSet for testing purposes.
/// It is particularly useful when you want to simulate the behavior of Entity Framework Core's DbSet
/// with custom expression handling, such as for testing LINQ queries or database operations.
/// The method takes an IQueryable of the entity type and returns a mocked DbSet that implements
/// both IAsyncEnumerable and IQueryable interfaces, allowing for asynchronous enumeration
/// and LINQ query capabilities.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity that the DbSet will represent.
/// </typeparam>
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity>(this IQueryable<TEntity> data) where TEntity : class
{
var mock = new Mock<DbSet<TEntity>>();
var enumerable = new TestAsyncEnumerableEfCore<TEntity>(data);
mock.ConfigureAsyncEnumerableCalls(enumerable);
mock.As<IQueryable<TEntity>>().ConfigureQueryableCalls(enumerable, data);
mock.As<IAsyncEnumerable<TEntity>>().Setup(x => x.GetAsyncEnumerator(It.IsAny<CancellationToken>())).Returns(() => enumerable.GetAsyncEnumerator());
mock.Setup(m => m.AsQueryable()).Returns(enumerable);
return BuildMockDbSet<TEntity, TestExpressionVisitor>(data);
}

mock.ConfigureDbSetCalls(data);
return mock;
}
/// <summary>
/// See <see cref="BuildMockDbSet{TEntity}"/>.
/// </summary>
/// <typeparam name="TEntity">
/// The type of the entity that the DbSet will represent.
/// </typeparam>
/// <typeparam name="TExpressionVisitor">
/// The type of the expression visitor that will be used to process LINQ expressions.
/// Can be used to mock EF Core specific expression handling, such as for ILike expressions.
/// </typeparam>
public static Mock<DbSet<TEntity>> BuildMockDbSet<TEntity, TExpressionVisitor>(this IQueryable<TEntity> data)
where TEntity : class
where TExpressionVisitor : ExpressionVisitor, new()
{
var mock = new Mock<DbSet<TEntity>>();
var enumerable = new TestAsyncEnumerableEfCore<TEntity, TExpressionVisitor>(data);
mock.ConfigureAsyncEnumerableCalls(enumerable);
mock.As<IQueryable<TEntity>>().ConfigureQueryableCalls(enumerable, data);
mock.As<IAsyncEnumerable<TEntity>>().Setup(x => x.GetAsyncEnumerator(It.IsAny<CancellationToken>())).Returns(() => enumerable.GetAsyncEnumerator());
mock.Setup(m => m.AsQueryable()).Returns(enumerable);

mock.ConfigureDbSetCalls(data);
return mock;
}

private static void ConfigureDbSetCalls<TEntity>(this Mock<DbSet<TEntity>> mock, IQueryable<TEntity> data)
private static void ConfigureDbSetCalls<TEntity>(this Mock<DbSet<TEntity>> mock, IQueryable<TEntity> data)
where TEntity : class
{
mock.Setup(m => m.AsQueryable()).Returns(mock.Object);
Expand Down
Loading