Standard collaboration patterns for all squad agents — worktree awareness, decisions, cross-agent communication
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
How to update Moq mock setups when a manager method gains new parameters — prevents silent overload mismatch bugs that cause MockException or wrong test results
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
When a manager interface method gains new parameters (e.g., ownerOid added as part of Epic #609's per-user ownership enforcement), every existing Moq .Setup() call that targets the old signature becomes a dead setup. Moq will either:
null for reference types, 0 for value types, false for bool). The test passes for the wrong reason, or fails with a NullReferenceException that is hard to diagnose.MockException — when .Verifiable() is in play and the expected call never fired, the verify step throws.This is a silent failure mode: the test compiles, may even pass, but is not testing what you think it's testing.
Project: JJGNET Broadcasting
Framework: xUnit + Moq 4.20.72 + FluentAssertions
Trigger: Any manager method signature change (new parameter added, parameter type changed, parameter removed)
GetAllAsync gains ownerOid parameterBefore Epic #609, the manager interface was:
// IScheduledItemManager — BEFORE
Task<PagedResult<ScheduledItem>> GetAllAsync(int page, int pageSize);
After Epic #609, a new overload was added for owner-filtered access:
// IScheduledItemManager — AFTER (two overloads)
Task<PagedResult<ScheduledItem>> GetAllAsync(int page, int pageSize); // admin/unfiltered
Task<PagedResult<ScheduledItem>> GetAllAsync(string ownerOid, int page, int pageSize, ...); // owner-filtered
The controller now calls the owner-filtered overload for non-admin users:
// SchedulesController (after Epic #609)
var result = await _scheduledItemManager.GetAllAsync(currentUserOid, page, pageSize);
The old mock setup targets the unfiltered overload (2 parameters). The controller now calls the owner-filtered overload (3+ parameters). Moq finds no matching setup.
// ❌ BROKEN — targets old 2-parameter overload; controller now calls 3-parameter overload
_scheduledItemManagerMock
.Setup(m => m.GetAllAsync(It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(new PagedResult<ScheduledItem> { Items = items, TotalCount = 1 });
var sut = CreateSut(Domain.Scopes.Schedules.All, ownerOid: "test-oid-12345");
var result = await sut.GetScheduledItemsAsync();
// result.Value is null — the controller called the 3-parameter overload, no setup matched.
// The test may fail with NullReferenceException or, worse, produce a false positive.
result.Value.Should().NotBeNull(); // ← throws NullReferenceException
Symptom: NullReferenceException, MockException, or (worst case) a test that "passes" because the null result flows through a code path that returns an empty 200 OK.
Match the overload that the controller actually calls. Use It.IsAny<T>() for the new parameter unless the test specifically needs to assert its value.
// ✅ FIXED — targets the 3-parameter owner-filtered overload that the controller now calls
_scheduledItemManagerMock
.Setup(m => m.GetAllAsync(
It.IsAny<string>(), // ownerOid — new parameter; use IsAny<> unless test verifies it
It.IsAny<int>(), // page
It.IsAny<int>())) // pageSize
.ReturnsAsync(new PagedResult<ScheduledItem> { Items = items, TotalCount = 1 });
var sut = CreateSut(Domain.Scopes.Schedules.All, ownerOid: "test-oid-12345");
var result = await sut.GetScheduledItemsAsync();
// Now resolves correctly — setup matches the actual call.
result.Value.Should().NotBeNull();
result.Value!.Items.Should().HaveCount(1);
Use It.IsAny<string>() for the new parameter in most tests. Use an explicit value only when the test is specifically verifying that the controller passes the correct OID:
// ✅ Verifying that the controller passes the caller's OID to the manager
_scheduledItemManagerMock
.Setup(m => m.GetAllAsync(
"test-oid-12345", // exact OID — this test verifies the controller uses the caller's OID
It.IsAny<int>(),
It.IsAny<int>()))
.ReturnsAsync(new PagedResult<ScheduledItem> { Items = items, TotalCount = 1 });
// Then verify after Act:
_scheduledItemManagerMock.Verify(
m => m.GetAllAsync("test-oid-12345", It.IsAny<int>(), It.IsAny<int>()),
Times.Once);
Default rule: Use It.IsAny<T>() for new parameters unless there is an explicit test objective around that parameter's value.
When a manager method signature changes:
Find all affected .Setup() calls:
Select-String -Path ".\src\**\*Tests.cs" -Pattern "\.Setup\(m => m\.GetAllAsync" -Recurse
For each match, check the parameter count against the new interface.
Add It.IsAny<T>() for each new parameter (unless the test needs to assert the specific value).
Run the specific test class immediately to verify the fix before moving on:
dotnet test .\src\ --filter "FullyQualifiedName~SchedulesControllerTests" --no-build --verbosity normal
Run the full test suite before pushing:
dotnet test .\src\ --no-build --verbosity normal --filter "FullyQualifiedName!~SyndicationFeedReader"
When a controller has an IsSiteAdministrator() branch that calls the unfiltered overload, both setups must exist simultaneously in the same test class:
// Setup for non-admin tests (owner-filtered overload):
_scheduledItemManagerMock
.Setup(m => m.GetAllAsync(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(pagedResult);
// Setup for admin tests (unfiltered overload):
_scheduledItemManagerMock
.Setup(m => m.GetAllAsync(It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(pagedResult);
Then in admin-bypass tests, verify that the unfiltered overload fires and the owner-filtered one does not:
_scheduledItemManagerMock.Verify(
m => m.GetAllAsync(It.IsAny<int>(), It.IsAny<int>()),
Times.Once);
_scheduledItemManagerMock.Verify(
m => m.GetAllAsync(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()),
Times.Never);
This is the same pattern used in the security-test-checklist SKILL for admin bypass verification — the two skills are complementary.
// WRONG — setup for old overload; controller calls new overload → null result
_managerMock.Setup(m => m.GetAllAsync(It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(result);
// WRONG — if the test OID ever changes, this setup silently stops matching
_managerMock.Setup(m => m.GetAllAsync("hardcoded-oid", It.IsAny<int>(), It.IsAny<int>()))
.ReturnsAsync(result);
// WRONG workflow: update setup → push → find out CI failed
// RIGHT workflow: update setup → dotnet test --filter "FullyQualifiedName~XTests" → green → push
Moq setups are resolved at runtime, not compile time. A setup for the wrong overload compiles without warning. Always run the test after a setup change.
GetAllAsync(ownerOid, page, size) alongside the existing GetAllAsync(page, size) on all four manager interfacesIScheduledItemManager, IEngagementManager, ITalkManager, IMessageTemplateManager*ControllerTests.cs files in src/JosephGuadagno.Broadcasting.Api.Tests/Controllers/아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Standard collaboration patterns for all squad agents — worktree awareness, decisions, cross-agent communication
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Shared hard rules enforced across all squad agents
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Comprehensive AI prompt engineering safety review and improvement prompt. Analyzes prompts for safety, bias, security vulnerabilities, and effectiveness while providing detailed improvement recommendations with extensive frameworks, testing methodologies, and educational content.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Migrate API controller scope checks to framework-enforced authorization policies while preserving ownership checks.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Review API DTO implementations for contract/domain separation, mapping patterns, and REST compliance
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Register shared claims transformation and hierarchical role policies in the API without breaking existing scope enforcement.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.