This repository contains MockQueryable, a .NET library for mocking IQueryable<T> and EF Core async query APIs in unit tests without a real database.
Use this file as the first-stop guide for future Codex tasks in this repo.
src/MockQueryable/MockQueryable.slnThe main solution.src/MockQueryable/MockQueryable.CoreCore query-provider engine. ContainsTestQueryProvider<T, TExpressionVisitor>and the defaultTestExpressionVisitor.src/MockQueryable/MockQueryable.EntityFrameworkCoreEF Core integration layer. AddsIAsyncEnumerable<T>,IAsyncQueryProvider, async enumeration, and interception forExecuteUpdate/ExecuteDelete.src/MockQueryable/MockQueryable.MoqMoq-specific extensions such asBuildMockDbSet.src/MockQueryable/MockQueryable.NSubstituteNSubstitute-specific extensions such asBuildMockDbSet.src/MockQueryable/MockQueryable.FakeItEasyFakeItEasy-specific extensions such asBuildMockDbSet.src/MockQueryable/MockQueryable.SampleSample usage plus the test suite. Tests are written with NUnit and exercise all supported mocking libraries.docs/ARCHITECTURE.mdLonger architecture explainer..github/workflowsCI and release workflows.
High-level flow:
- Tests start from an in-memory
ICollection<T>orList<T>. BuildMock()orBuildMockDbSet()wraps that collection in custom query types.MockQueryable.Corecaptures and executes LINQ expression trees in memory.MockQueryable.EntityFrameworkCoreprovides EF Core-compatible async execution and async enumeration.- Optional custom
ExpressionVisitorimplementations rewrite provider-specific calls likeLike/ILike. - Mocking-library adapters expose the same behavior through Moq, NSubstitute, and FakeItEasy.
Important implementation details:
TestQueryProvider<T, TExpressionVisitor>is the core execution engine. It rewrites expressions withTExpressionVisitor, compiles them, and executes them against in-memory data.TestAsyncEnumerableEfCore<T, TExpressionVisitor>is the EF Core bridge. It implementsIAsyncEnumerable<T>andIAsyncQueryProvider.ExecuteUpdateAsyncandExecuteDeleteAsyncare simulated in memory by intercepting those method calls and mutating/removing items from the backing collection.MockQueryableExtensions.BuildMock()intentionally lives in namespaceMockQueryable, notMockQueryable.EntityFrameworkCore, to preserve compatibility with earlier extension method locations.
Run from repository root C:\Project2\MockQueryable.
Restore:
dotnet restore src\MockQueryable\MockQueryable.slnBuild the solution in Release:
dotnet build src\MockQueryable\MockQueryable.sln --configuration Release --no-restorePack NuGet packages:
dotnet pack src\MockQueryable\MockQueryable.sln --configuration Release --no-build --output .\artifactsNotes:
- CI uses .NET SDK
11.0.x. - All projects currently target
net11.0. - Library projects have
GeneratePackageOnBuild=true, sodotnet buildalso produces package artifacts.
Run the full test suite:
dotnet test src\MockQueryable\MockQueryable.sln --configuration Release --no-buildRun tests with coverage, matching CI behavior:
dotnet test src\MockQueryable\MockQueryable.sln --configuration Release --no-build /p:CollectCoverage=true /p:CoverletOutput=TestResults\coverage.opencover.xml /p:CoverletOutputFormat=opencoverRun only the sample/test project:
dotnet test src\MockQueryable\MockQueryable.Sample\MockQueryable.Sample.csproj --configuration ReleaseFollow the existing codebase rather than introducing a new style system.
- Language/runtime:
Use modern C# compatible with
.NET 11. - Namespaces: Prefer file-scoped namespaces where the surrounding project already uses them. Some older files still use block-scoped namespaces; preserve local style when editing.
- Usings:
Keep explicit
usingdirectives. There is no repo-level config enabling implicit usings. - Typing:
varis used when the type is obvious; explicit types are also common. Match nearby code. - Formatting: Use 4-space indentation and keep braces/spacing consistent with the file you are editing.
- Comments: Keep comments sparse and only where behavior is non-obvious.
- Tests:
NUnit is the test framework. Test names are descriptive and commonly use patterns like
Method_Scenario_ExpectedResult. - API design:
Keep the public surface small and framework-agnostic where possible. Mocking-library-specific behavior belongs in the adapter projects, not in
Core.
TestQueryProvider<T, TExpressionVisitor>: Small changes here affect all query execution paths. Be careful with expression compilation, deferred execution, and provider creation.TestAsyncEnumerableEfCore<T, TExpressionVisitor>: This class simulates EF Core behavior with reflection and expression inspection. Changes can silently break async operators,ExecuteDeleteAsync, orExecuteUpdateAsync.- Namespace placement of
BuildMock(): The extension method inMockQueryable.EntityFrameworkCore/MockQueryableExtensions.csis intentionally declared undernamespace MockQueryablefor backward compatibility. Do not "clean this up" without treating it as a breaking API change. - Expression visitor behavior:
The sample
SampleLikeExpressionVisitordemonstrates provider-specific rewriting. Changes to visitor flow can breakLike/ILikesimulation and other custom visitor scenarios. - Backing collection mutation:
BuildMockDbSet()implementations rely on the originalICollection<T>remaining mutable so delete/update behavior can reflect back into the source list. - Cross-framework parity: Moq, NSubstitute, and FakeItEasy adapters should behave consistently. When changing one adapter, review the others.
- Packaging metadata:
Version, package identity, README/license packaging, and icons are duplicated across multiple
.csprojfiles. Keep them aligned.
- Read
docs/ARCHITECTURE.mdbefore changing core behavior. - For feature work, start by identifying whether the change belongs in:
Core,EntityFrameworkCore, or one of the mocking adapters. - For bug fixes involving query behavior, add or update tests in
MockQueryable.Samplefirst. - For API changes, check whether README examples and all three mocking adapters need updates.
- Avoid broad refactors across all projects unless the task explicitly asks for them.
Before finishing a task when possible:
- Run the most targeted
dotnet testcommand that covers the change. - If public API or packaging changed, run solution-level
dotnet buildordotnet pack. - If build/test is blocked by the current
obj/write issue, report that clearly instead of guessing.
- This is a NuGet library.
- Do not rename public types, methods, namespaces, or packages unless explicitly requested.
- Preserve backward compatibility.
- Any behavioral change must include tests.
- Prefer additive changes over breaking changes.
When asked to increase coverage:
- Prefer adding tests for existing public behavior.
- Do not change production code unless explicitly required.
- Add regression tests around async query execution and provider-specific behavior.
- Keep tests focused and readable.
- Avoid snapshot-style tests unless already used.
- Do not run
dotnet buildordotnet test.
- Do not create, edit, or delete README files or any other project documentation without the user's explicit approval for those documentation changes.
- Authorization to change code, dependencies, build settings, or CI does not authorize documentation changes. Propose the documentation changes and obtain approval first.
- This rule also applies to AGENTS.md. Apply documentation changes directly when the user has explicitly requested or already approved them.