Refactor nunit tests off EF InMemory provider and web server

Extract IMicCheckDbContext so DB access can be mocked with Moq instead
of the InMemory provider, per project testing guidelines. Add shared
async DbSet test doubles (TestSupport/) to back the mocks. Rewrite
HTTP-pipeline tests (WebApplicationFactory) to exercise controllers
and auth handlers directly instead of running the web server. Drop
DbContext/seeder tests that only made sense against a real provider,
and remove stale duplicate test files left over from a namespace move.
This commit is contained in:
2026-07-04 19:54:22 -07:00
parent 1556b486d2
commit b4a666ebf0
53 changed files with 1293 additions and 2340 deletions

View File

@@ -1,8 +1,10 @@
using MicCheck.Api.Audit;
using MicCheck.Api.Common;
using MicCheck.Api.Data;
using MicCheck.Api.Organizations;
using MicCheck.Api.Projects;
using MicCheck.Api.Segments;
using Microsoft.EntityFrameworkCore;
using MicCheck.Api.Tests.Unit.TestSupport;
using Moq;
using NUnit.Framework;
@@ -11,7 +13,10 @@ namespace MicCheck.Api.Tests.Unit.Segments;
[TestFixture]
public class SegmentServiceTests
{
private MicCheckDbContext _db = null!;
private Mock<IMicCheckDbContext> _db = null!;
private List<Segment> _segments = null!;
private List<SegmentRule> _segmentRules = null!;
private List<SegmentCondition> _segmentConditions = null!;
private SegmentService _service = null!;
private const int OrganizationId = 1;
private const int ProjectId = 1;
@@ -19,43 +24,30 @@ public class SegmentServiceTests
[SetUp]
public void SetUp()
{
var options = new DbContextOptionsBuilder<MicCheckDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
_db = new MicCheckDbContext(options);
_db = new Mock<IMicCheckDbContext>();
_db.SetupDbSet(c => c.Organizations, [
new Organization { Id = OrganizationId, Name = "Test Org", CreatedAt = DateTimeOffset.UtcNow }
]);
_db.SetupDbSet(c => c.Projects, [
new Project { Id = ProjectId, Name = "Test Project", OrganizationId = OrganizationId, CreatedAt = DateTimeOffset.UtcNow }
]);
_segments = [];
_db.SetupDbSetWithGeneratedIds(c => c.Segments, _segments);
_segmentRules = [];
_db.SetupDbSetWithGeneratedIds(c => c.SegmentRules, _segmentRules);
_segmentConditions = [];
_db.SetupDbSetWithGeneratedIds(c => c.SegmentConditions, _segmentConditions);
var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue();
var auditService = new Mock<AuditService>(_db, null!, webhookQueue);
var auditService = new Mock<AuditService>(_db.Object, null!, webhookQueue);
auditService.Setup(a => a.LogAsync(
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<int>(), It.IsAny<int?>(), It.IsAny<int?>(),
It.IsAny<string?>(), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
_service = new SegmentService(_db, auditService.Object);
SeedBaseData();
}
[TearDown]
public void TearDown() => _db.Dispose();
private void SeedBaseData()
{
_db.Organizations.Add(new MicCheck.Api.Organizations.Organization
{
Id = OrganizationId,
Name = "Test Org",
CreatedAt = DateTimeOffset.UtcNow
});
_db.Projects.Add(new MicCheck.Api.Projects.Project
{
Id = ProjectId,
Name = "Test Project",
OrganizationId = OrganizationId,
CreatedAt = DateTimeOffset.UtcNow
});
_db.SaveChanges();
_service = new SegmentService(_db.Object, auditService.Object);
}
private static SegmentRuleDefinition SingleConditionRule(
@@ -71,36 +63,33 @@ public class SegmentServiceTests
var rules = new[] { SingleConditionRule() };
var segment = await _service.CreateAsync(ProjectId, "Premium Users", rules);
var loaded = await _db.Segments
.Include(s => s.Rules)
.ThenInclude(r => r.Conditions)
.FirstAsync(s => s.Id == segment.Id);
var persistedRules = _segmentRules.Where(r => r.SegmentId == segment.Id).ToList();
Assert.That(loaded.Name, Is.EqualTo("Premium Users"));
Assert.That(loaded.Rules, Has.Count.EqualTo(1));
Assert.That(loaded.Rules.First().Conditions, Has.Count.EqualTo(1));
Assert.That(segment.Name, Is.EqualTo("Premium Users"));
Assert.That(persistedRules, Has.Count.EqualTo(1));
Assert.That(persistedRules[0].Conditions, Has.Count.EqualTo(1));
}
[Test]
public async Task WhenProjectHas100Segments_ThenCreatingAnotherThrowsDomainException()
public void WhenProjectHas100Segments_ThenCreatingAnotherThrowsDomainException()
{
for (var i = 0; i < 100; i++)
{
_db.Segments.Add(new Segment
_segments.Add(new Segment
{
Id = i + 1,
Name = $"segment_{i}",
ProjectId = ProjectId,
CreatedAt = DateTimeOffset.UtcNow
});
}
_db.SaveChanges();
Assert.ThrowsAsync<DomainException>(() =>
_service.CreateAsync(ProjectId, "overflow_segment", [SingleConditionRule()]));
}
[Test]
public async Task WhenSegmentHasMoreThan100Conditions_ThenCreatingThrowsDomainException()
public void WhenSegmentHasMoreThan100Conditions_ThenCreatingThrowsDomainException()
{
var conditions = Enumerable.Range(0, 101)
.Select(i => new SegmentConditionDefinition($"prop_{i}", SegmentConditionOperator.Equal, "val"))
@@ -119,14 +108,11 @@ public class SegmentServiceTests
var newRules = new[] { SingleConditionRule("country") };
var updated = await _service.UpdateAsync(segment.Id, "Updated Segment", newRules);
var loaded = await _db.Segments
.Include(s => s.Rules)
.ThenInclude(r => r.Conditions)
.FirstAsync(s => s.Id == updated.Id);
var persistedRules = _segmentRules.Where(r => r.SegmentId == updated.Id).ToList();
Assert.That(loaded.Name, Is.EqualTo("Updated Segment"));
Assert.That(loaded.Rules, Has.Count.EqualTo(1));
Assert.That(loaded.Rules.First().Conditions.First().Property, Is.EqualTo("country"));
Assert.That(updated.Name, Is.EqualTo("Updated Segment"));
Assert.That(persistedRules, Has.Count.EqualTo(1));
Assert.That(persistedRules[0].Conditions.First().Property, Is.EqualTo("country"));
}
[Test]
@@ -136,7 +122,7 @@ public class SegmentServiceTests
await _service.DeleteAsync(segment.Id);
var found = await _db.Segments.FirstOrDefaultAsync(s => s.Id == segment.Id);
var found = _segments.FirstOrDefault(s => s.Id == segment.Id);
Assert.That(found, Is.Null);
}