Instruction file imported from hebelmx/ExxerRules (
.cursor/rules/0304_EntityFrameworkCoreTesting.mdc). Copyright stays with the author.
EntityFrameworkCore Testing Standards
Meta
Title: EntityFrameworkCore Testing Standards
Description: Establishes comprehensive testing standards for Entity Framework Core using in-memory database and mocking patterns
Created-at: 2025-01-05T23:25:00Z
Last-updated-at: 2025-01-05T23:25:00Z
Applies-to: EF Core testing, database testing, repository testing, integration testing
File-matcher: *.cs files with EF Core DbContext usage
Requirements
Description: Use InMemory database provider for EF Core testing instead of mocking DbContext
Examples:
[Fact]
public async Task Should_SaveOrder_When_ValidOrderProvided()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new OrderDbContext(options);
var repository = new OrderRepository(context);
var order = new Order { CustomerName = "John Doe", TotalAmount = 100.00m };
// Act
var result = await repository.SaveAsync(order);
// Assert
result.IsSuccess.ShouldBeTrue();
context.Orders.ShouldContain(o => o.CustomerName == "John Doe");
}
var repository = new OrderRepository(mockContext);
var order = new Order { CustomerName = "John Doe", TotalAmount = 100.00m };
// Act
var result = await repository.SaveAsync(order);
// Assert
result.IsSuccess.ShouldBeTrue(); // Mocked - not real database test
}
</incorrect-example>
</requirement>
### <requirement priority="high">
**Description**: Use EntityFrameworkCore.Testing library for complex relational operations when needed
**Examples**:
<correct-example>
```csharp
[Fact]
public async Task Should_ExecuteStoredProcedure_When_ValidParametersProvided()
{
// Arrange
var expectedResult = new List<Order> { new Order { Id = Guid.NewGuid() } };
var mockedDbContext = Create.MockedDbContextFor<OrderDbContext>();
mockedDbContext.Set<Order>().AddFromSqlRawResult(expectedResult);
var repository = new OrderRepository(mockedDbContext);
// Act
var result = await repository.GetOrdersByCustomerAsync(customerId);
// Assert
result.ShouldBeEquivalentTo(expectedResult);
}
var repository = new OrderRepository(mockContext);
// Act
var result = await repository.GetOrdersByCustomerAsync(customerId);
// Assert
result.ShouldNotBeNull(); // Mocked - doesn't test real stored procedure
}
</incorrect-example>
</requirement>
### <requirement priority="high">
**Description**: Ensure DbContext has virtual properties for proper testing support
**Examples**:
<correct-example>
```csharp
public class OrderDbContext : DbContext
{
public OrderDbContext(DbContextOptions<OrderDbContext> options) : base(options)
{
}
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Product> Products { get; set; }
}
public DbSet<Order> Orders { get; set; } // Missing virtual keyword
public DbSet<Customer> Customers { get; set; } // Missing virtual keyword
public DbSet<Product> Products { get; set; } // Missing virtual keyword
}
</incorrect-example>
</requirement>
### <requirement priority="medium">
**Description**: Use unique database names for each test to ensure isolation
**Examples**:
<correct-example>
```csharp
[Fact]
public async Task Should_CreateOrder_When_ValidOrderProvided()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new OrderDbContext(options);
var repository = new OrderRepository(context);
// Act & Assert
// Test implementation
}
[Fact]
public async Task Should_UpdateOrder_When_OrderExists()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) // Unique name
.Options;
using var context = new OrderDbContext(options);
var repository = new OrderRepository(context);
// Act & Assert
// Test implementation
}
using var context = new OrderDbContext(options);
var repository = new OrderRepository(context);
// Act & Assert
// Test implementation
}
[Fact] public async Task Should_UpdateOrder_When_OrderExists() { // Arrange var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: "TestDatabase") // Same shared name .Options;
using var context = new OrderDbContext(options);
var repository = new OrderRepository(context);
// Act & Assert
// Test implementation - may interfere with other test
}
</incorrect-example>
</requirement>
### <requirement priority="medium">
**Description**: Seed test data from SeedDataFiles for realistic testing scenarios
**Examples**:
<correct-example>
```csharp
[Fact]
public async Task Should_QueryOrdersByCustomer_When_CustomerHasOrders()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new OrderDbContext(options);
await SeedTestDataAsync(context); // Load from SeedDataFiles
var repository = new OrderRepository(context);
var customerId = Guid.Parse("12345678-1234-1234-1234-123456789012");
// Act
var orders = await repository.GetOrdersByCustomerAsync(customerId);
// Assert
orders.ShouldNotBeEmpty();
orders.All(o => o.CustomerId == customerId).ShouldBeTrue();
}
private static async Task SeedTestDataAsync(OrderDbContext context)
{
var customers = JsonSerializer.Deserialize<List<Customer>>(
File.ReadAllText("Src/SeedDataFiles/customers.json"));
var orders = JsonSerializer.Deserialize<List<Order>>(
File.ReadAllText("Src/SeedDataFiles/orders.json"));
context.Customers.AddRange(customers);
context.Orders.AddRange(orders);
await context.SaveChangesAsync();
}
using var context = new OrderDbContext(options);
// No seed data - empty database
var repository = new OrderRepository(context);
var customerId = Guid.NewGuid();
// Act
var orders = await repository.GetOrdersByCustomerAsync(customerId);
// Assert
orders.ShouldBeEmpty(); // Always empty due to no seed data
}
</incorrect-example>
</requirement>
### <requirement priority="high">
**Description**: Test complex queries and LINQ operations with realistic data
**Examples**:
<correct-example>
```csharp
[Fact]
public async Task Should_GetOrdersWithProducts_When_ComplexQueryExecuted()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new OrderDbContext(options);
await SeedTestDataAsync(context);
var repository = new OrderRepository(context);
// Act
var ordersWithProducts = await repository.GetOrdersWithProductsAsync();
// Assert
ordersWithProducts.ShouldNotBeEmpty();
ordersWithProducts.All(o => o.Items.Any()).ShouldBeTrue();
ordersWithProducts.All(o => o.Items.All(i => i.Product != null)).ShouldBeTrue();
}
var repository = new OrderRepository(mockContext);
// Act
var ordersWithProducts = await repository.GetOrdersWithProductsAsync();
// Assert
ordersWithProducts.ShouldNotBeNull(); // Mocked - doesn't test real query
}
</incorrect-example>
</requirement>
### <requirement priority="critical">
**Description**: Never mock SaveChanges - use real database operations
**Examples**:
<correct-example>
```csharp
[Fact]
public async Task Should_SaveChanges_When_OrderAdded()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new OrderDbContext(options);
var repository = new OrderRepository(context);
var order = new Order { CustomerName = "John Doe", TotalAmount = 100.00m };
// Act
var result = await repository.SaveAsync(order);
// Assert
result.IsSuccess.ShouldBeTrue();
context.Orders.ShouldContain(o => o.CustomerName == "John Doe");
context.Orders.Count().ShouldBe(1);
}
var repository = new OrderRepository(mockContext);
var order = new Order { CustomerName = "John Doe", TotalAmount = 100.00m };
// Act
var result = await repository.SaveAsync(order);
// Assert
result.IsSuccess.ShouldBeTrue(); // Mocked - doesn't test real persistence
}
</incorrect-example>
</requirement>
### <requirement priority="medium">
**Description**: Use proper disposal patterns for DbContext in tests
**Examples**:
<correct-example>
```csharp
[Fact]
public async Task Should_DisposeContext_When_TestCompletes()
{
// Arrange
var options = new DbContextOptionsBuilder<OrderDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
using var context = new OrderDbContext(options); // Proper disposal
var repository = new OrderRepository(context);
// Act & Assert
// Test implementation
}
var context = new OrderDbContext(options); // No disposal
var repository = new OrderRepository(context);
// Act & Assert
// Test implementation - context not disposed
}
</incorrect-example>
</requirement>
## Testing Strategy
### Database Testing
- **InMemory Provider**: Use for fast, isolated testing
- **Real Operations**: Test actual database operations
- **Seed Data**: Use realistic data from SeedDataFiles
- **Isolation**: Use unique database names per test
### Test Categories
- **Unit Tests**: Test individual repository methods
- **Integration Tests**: Test database interactions
- **Query Tests**: Test complex LINQ operations
- **Transaction Tests**: Test database transactions
### Best Practices
- **Avoid Mocking**: Use real database operations
- **Virtual Properties**: Ensure DbSet properties are virtual
- **Proper Disposal**: Use using statements for DbContext
- **Realistic Data**: Seed with realistic test data
## Context
This rule establishes comprehensive Entity Framework Core testing standards. It emphasizes:
1. **Real Database**: Use InMemory provider for realistic testing
2. **Avoid Mocking**: Test actual database operations
3. **Realistic Data**: Use seed data from files
4. **Proper Isolation**: Use unique database names
5. **Performance**: Fast testing with in-memory database
The rule prioritizes testing that validates real database behavior while maintaining fast test execution through the in-memory provider.
## References
<reference as="dependency" href=".cursor/rules/0000RuleToWriteRules.mdc" reason="Defines rule structure standards">Rule Writing Standards</reference>
<reference as="context" href=".cursor/rules/ApplicationUnitTest.mdc" reason="Application testing patterns">Application Testing Standards</reference>
<reference as="context" href=".cursor/rules/AgregationBoundedTest.mdc" reason="Aggregation testing patterns">Aggregation Testing Standards</reference>
var byPropertyDbSetMock = Mock.Get(mockedDbContext.TestEntities);
byPropertyDbSetMock.Verify(m => m.AddRange(It.IsAny<IEnumerable<TestEntity>>()), Times.Once);