EF Core 2下含DI DbContext与LINQ模型的控制器单元测试(PostGIS环境)
Got it, let's break down how to unit test your controllers in this specific setup—PostgreSQL with PostGIS, no repository pattern, direct DbContext usage with LINQ, and Database First. I've seen similar questions pop up before but none that cleanly address the no-repo scenario, so here's a straightforward, actionable approach:
Core Approach: Mock DbContext & DbSet (Skip InMemoryDb)
Since InMemoryDB doesn't support PostGIS geometric types, mocking your DbContext and DbSets with a library like Moq (plus Moq.EntityFrameworkCore for EF-specific helpers) is the way to go. This lets you simulate database interactions without needing a real PostgreSQL instance.
Step 1: Set Up Test Dependencies
First, make sure your test project has these NuGet packages installed:
Moq(for mocking)Moq.EntityFrameworkCore(simplifies mocking DbSets)Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite(to handle PostGIS geometric types in test data)NetTopologySuite(to create valid geometry objects for testing)
Step 2: Mock Your DbContext & Test Data
Assuming you have a Database First-generated DbContext and entity (e.g., a Location table with a PostGIS Point column), here's how to set up mocks for your controller tests:
Example Entity & DbContext (Database First)
// Auto-generated from your PostgreSQL database (Database First) public partial class Location { public int Id { get; set; } public NetTopologySuite.Geometries.Point Coordinates { get; set; } public string Name { get; set; } } public partial class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public virtual DbSet<Location> Locations { get; set; } }
Test Class Setup
using Moq; using Moq.EntityFrameworkCore; using NetTopologySuite.Geometries; using Xunit; // Or your test framework of choice public class LocationsControllerTests { private Mock<AppDbContext> _mockDbContext; private LocationsController _controller; public LocationsControllerTests() { // 1. Prepare test data with valid PostGIS geometry objects var testLocations = new List<Location> { new Location { Id = 1, Name = "Central Park", Coordinates = new Point(-73.968285, 40.785091) { SRID = 4326 } // WGS84 coordinate system }, new Location { Id = 2, Name = "Empire State Building", Coordinates = new Point(-73.9857, 40.7484) { SRID = 4326 } } }; // 2. Mock the DbContext and DbSet var dbOptions = new DbContextOptionsBuilder<AppDbContext>().Options; _mockDbContext = new Mock<AppDbContext>(dbOptions); _mockDbContext.Setup(c => c.Locations).ReturnsDbSet(testLocations); // 3. Initialize your controller with the mocked DbContext _controller = new LocationsController(_mockDbContext.Object); } [Fact] public async Task GetLocationsWithinRadius_ReturnsFilteredResults() { // Arrange: Define the test parameters (center point + radius in meters) var searchCenter = new Point(-73.97, 40.78) { SRID = 4326 }; const int radiusMeters = 5000; // Act: Call the controller method you want to test var result = await _controller.GetLocationsWithinRadius(searchCenter, radiusMeters); // Assert: Verify the result is correct var okResult = Assert.IsType<OkObjectResult>(result); var returnedLocations = Assert.IsType<List<Location>>(okResult.Value); // Only Central Park should be within 5km of the search center Assert.Single(returnedLocations); Assert.Equal("Central Park", returnedLocations[0].Name); } }
Key Notes for Your Setup
- Handle PostGIS Types Properly: Always set the correct SRID (e.g., 4326 for WGS84) on your test geometry objects—this matches how your production database stores data, ensuring LINQ spatial queries work as expected in mocks.
- Database First Best Practices: Keep your auto-generated entity classes in a shared project so both your main app and test project can reference them. If you need to extend entities for testing, use partial classes (since Database First generates partial classes by default).
- Validate Query Logic: For complex LINQ queries (e.g., spatial filters + sorting + pagination), you can verify that your DbSet was accessed correctly using Moq's verification methods:
_mockDbContext.Verify(c => c.Locations, Times.Once); - Async Support: Moq.EntityFrameworkCore handles async EF methods like
ToListAsync()out of the box, so you don't need extra setup for async controller actions.
Why This Works Better Than InMemoryDb
InMemoryDb doesn't support PostGIS-specific types or spatial LINQ operations, so mocking avoids that limitation entirely. It also keeps your tests fast and isolated—no need to spin up a test PostgreSQL instance or manage database state between tests.
内容的提问来源于stack exchange,提问作者thefolenangel

