Skip to content

Commit 56a1e8f

Browse files
committed
update
1 parent 5c473c0 commit 56a1e8f

9 files changed

Lines changed: 82 additions & 76 deletions

File tree

.codex/skills/dotnet-xunit/references/anti-patterns.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ public class UserServiceTests
6565
}
6666
```
6767

68-
## Excessive Mocking
68+
## Test Doubles Instead of Behavior
6969

70-
Over-mocking creates brittle tests that verify implementation rather than behavior.
70+
Stub, Fake, and Mock doubles are forbidden by default. They create brittle tests when they replace behavior that should be proven through real implementations, Aspire-managed resources, or observable state.
7171

7272
Bad:
7373

@@ -114,9 +114,9 @@ public class OrderServiceTests
114114
[Fact]
115115
public void PlaceOrder_ValidOrder_ReturnsOrderId()
116116
{
117-
// Arrange - use real implementations where cheap, mock only external boundaries
117+
// Arrange - use real implementations and observable state
118118
var repository = new InMemoryOrderRepository();
119-
var notificationService = Substitute.For<INotificationService>();
119+
var notificationService = new RecordingNotificationService();
120120
var service = new OrderService(repository, notificationService);
121121
var order = new Order { CustomerId = 1, Items = [new OrderItem { ProductId = 1, Quantity = 1 }] };
122122

@@ -128,6 +128,7 @@ public class OrderServiceTests
128128
var savedOrder = repository.GetById(orderId);
129129
Assert.NotNull(savedOrder);
130130
Assert.Equal(OrderStatus.Placed, savedOrder.Status);
131+
Assert.Contains(notificationService.SentOrderIds, id => id == orderId);
131132
}
132133
}
133134
```
@@ -188,7 +189,7 @@ public class CacheTests
188189
// Arrange
189190
var store = new InMemoryDataStore();
190191
store.Set("key", "original");
191-
var timeProvider = new FakeTimeProvider();
192+
var timeProvider = new ControlledTimeProvider();
192193
var cache = new Cache(store, timeProvider, ttl: TimeSpan.FromMinutes(5));
193194

194195
// Act
@@ -320,7 +321,7 @@ public class TimestampTests
320321
{
321322
// Arrange
322323
var fixedTime = new DateTimeOffset(2024, 1, 15, 10, 30, 0, TimeSpan.Zero);
323-
var timeProvider = new FakeTimeProvider(fixedTime);
324+
var timeProvider = new ControlledTimeProvider(fixedTime);
324325
var service = new RecordService(timeProvider);
325326

326327
// Act
@@ -536,14 +537,14 @@ public class UserRegistrationTests
536537
public void RegisterUser_ValidData_SendsWelcomeEmail()
537538
{
538539
// Arrange
539-
var emailService = Substitute.For<IEmailService>();
540+
var emailService = new RecordingEmailService();
540541
var service = new UserService(emailService);
541542

542543
// Act
543544
service.Register("test@example.com", "password123");
544545

545546
// Assert
546-
emailService.Received(1).SendWelcomeEmail("test@example.com");
547+
Assert.Contains("test@example.com", emailService.WelcomeRecipients);
547548
}
548549

549550
[Fact]

.codex/skills/dotnet-xunit/references/patterns.md

Lines changed: 17 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -214,75 +214,33 @@ public class StringUtilityTests
214214
}
215215
```
216216

217-
## Mocking with NSubstitute
217+
## Aspire-First Boundary Tests
218218

219-
Prefer NSubstitute for readable substitute configuration:
219+
For hosted, integration, browser, or infrastructure-backed behavior, prefer an Aspire-managed AppHost and real resources. Stub, Fake, and Mock doubles are forbidden by default; use them only with a documented exception and removal plan.
220220

221221
```csharp
222-
public class OrderServiceTests(ITestOutputHelper output)
222+
public class CheckoutFlowTests
223223
{
224224
[Fact]
225-
public async Task PlaceOrder_ValidOrder_SendsNotification()
225+
public async Task Checkout_ValidCart_ReturnsReceipt()
226226
{
227-
// Arrange
228-
var notificationService = Substitute.For<INotificationService>();
229-
var orderRepository = Substitute.For<IOrderRepository>();
230-
orderRepository.SaveAsync(Arg.Any<Order>()).Returns(Task.FromResult(true));
227+
// Arrange - resolve a client from the Aspire-managed test host
228+
await using var app = await DistributedApplicationTestingBuilder
229+
.CreateAsync<Projects.MyApp_AppHost>();
230+
await using var host = await app.BuildAsync();
231+
await host.StartAsync();
231232

232-
var service = new OrderService(orderRepository, notificationService);
233-
var order = new Order { CustomerId = 1, Items = [new OrderItem { ProductId = 1, Quantity = 2 }] };
233+
var client = host.CreateHttpClient("api");
234+
await host.ResourceNotifications.WaitForResourceHealthyAsync("api");
234235

235236
// Act
236-
await service.PlaceOrderAsync(order);
237-
238-
// Assert
239-
await notificationService.Received(1).SendOrderConfirmationAsync(Arg.Is<Order>(o => o.CustomerId == 1));
240-
output.WriteLine("Order placed and notification sent");
241-
}
242-
243-
[Fact]
244-
public async Task PlaceOrder_RepositoryFails_ThrowsException()
245-
{
246-
// Arrange
247-
var notificationService = Substitute.For<INotificationService>();
248-
var orderRepository = Substitute.For<IOrderRepository>();
249-
orderRepository.SaveAsync(Arg.Any<Order>()).ThrowsAsync(new DataException("Connection failed"));
250-
251-
var service = new OrderService(orderRepository, notificationService);
252-
var order = new Order { CustomerId = 1 };
253-
254-
// Act & Assert
255-
await Assert.ThrowsAsync<DataException>(() => service.PlaceOrderAsync(order));
256-
await notificationService.DidNotReceive().SendOrderConfirmationAsync(Arg.Any<Order>());
257-
}
258-
}
259-
```
237+
using var response = await client.PostAsJsonAsync("/checkout", new { Sku = "starter", Quantity = 1 });
238+
var receipt = await response.Content.ReadFromJsonAsync<Receipt>();
260239

261-
## Mocking with Moq
262-
263-
Use Moq when the project already depends on it:
264-
265-
```csharp
266-
public class PaymentProcessorTests
267-
{
268-
[Fact]
269-
public async Task ProcessPayment_ValidCard_ReturnsSuccess()
270-
{
271-
// Arrange
272-
var gatewayMock = new Mock<IPaymentGateway>();
273-
gatewayMock
274-
.Setup(g => g.ChargeAsync(It.IsAny<string>(), It.Is<decimal>(d => d > 0)))
275-
.ReturnsAsync(new PaymentResult { Success = true, TransactionId = "TX123" });
276-
277-
var processor = new PaymentProcessor(gatewayMock.Object);
278-
279-
// Act
280-
var result = await processor.ProcessPaymentAsync("4111111111111111", 99.99m);
281-
282-
// Assert
283-
Assert.True(result.Success);
284-
Assert.Equal("TX123", result.TransactionId);
285-
gatewayMock.Verify(g => g.ChargeAsync("4111111111111111", 99.99m), Times.Once);
240+
// Assert - verify observable behavior, not implementation calls
241+
response.EnsureSuccessStatusCode();
242+
Assert.NotNull(receipt);
243+
Assert.NotEqual(Guid.Empty, receipt.Id);
286244
}
287245
}
288246
```
@@ -389,4 +347,3 @@ dotnet test --filter "Category!=Integration"
389347

390348
- [xUnit.net Theory Data](https://xunit.net/docs/getting-started/v3/theory-data)
391349
- [xUnit.net Shared Context](https://xunit.net/docs/shared-context)
392-
- [NSubstitute Documentation](https://nsubstitute.github.io/help/getting-started/)

.codex/skills/dotnet-xunit/references/xunit.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ VSTest compatibility package when the repo intentionally uses that runner:
1919
dotnet add package xunit.runner.visualstudio
2020
```
2121

22+
Aspire CLI is mandatory alongside MCAF/.NET test setup when the repo has integration, browser, hosted, or infrastructure-backed tests:
23+
24+
```bash
25+
curl -sSL https://aspire.dev/install.sh | bash
26+
aspire --version
27+
```
28+
29+
Do not add mocking libraries as part of default xUnit setup. Stub, Fake, and Mock doubles are forbidden by default and require a documented exception with a removal plan.
30+
2231
## Verify First
2332

2433
Before adding packages, check what the repo already references:

.codex/skills/mcaf-solution-governance/references/dotnet-agents-pattern.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
Use this reference when the solution stack is .NET and the root or local `AGENTS.md` needs concrete command and tooling guidance.
44
The `.NET` skill bundle itself lives in the [Managed Code Skills catalog](https://skills.managed-code.com/), not in this repository.
55

6+
When installing or documenting MCAF for a .NET repository, also install Aspire CLI whenever the repo has integration, browser, hosted, or infrastructure-backed tests:
7+
8+
```bash
9+
curl -sSL https://aspire.dev/install.sh | bash
10+
aspire --version
11+
```
12+
613
## Root AGENTS.md Expectations
714

815
Record the real commands, not placeholders:
@@ -76,6 +83,8 @@ For each .NET project or module, record:
7683
- the active test framework
7784
- the active runner model
7885
- the coverage driver if the module runs coverage in isolation
86+
- the Aspire command when the module owns integration, browser, hosted, or infrastructure-backed tests
87+
- the local rule that Stub, Fake, and Mock doubles are forbidden by default and require a documented exception
7988

8089
Good `Applicable Skills` examples:
8190

.codex/skills/mcaf-solution-governance/references/project-agents-template.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Parent: `../AGENTS.md`
2929
- `test`: `...`
3030
- `format`: `...`
3131
- `analyze`: `...` (delete if not used)
32+
- `aspire`: `aspire run ...` (required when this project owns integration tests, browser tests, or infrastructure-backed tests)
3233

3334
For .NET projects also document:
3435

@@ -42,6 +43,13 @@ For .NET projects also document:
4243
- `...`
4344

4445
For .NET projects, install the needed `.NET` skills from the [Managed Code Skills catalog](https://skills.managed-code.com/).
46+
Aspire CLI is mandatory with MCAF/.NET setup when the repo has integration, browser, hosted, or infrastructure-backed tests. Install and validate it:
47+
48+
```bash
49+
curl -sSL https://aspire.dev/install.sh | bash
50+
aspire --version
51+
```
52+
4553
The local skill list usually includes:
4654

4755
- `mcaf-testing`
@@ -63,3 +71,7 @@ The local skill list usually includes:
6371

6472
- Project-specific rules go here.
6573
- Local rules may tighten root rules, but must not weaken them silently.
74+
- Stub, Fake, and Mock doubles are forbidden by default; avoid them and use real implementations, Aspire-managed resources, public APIs, or user-visible flows.
75+
- If a Stub, Fake, or Mock is unavoidable, document why in the nearest test or durable doc and include a removal plan.
76+
- Integration tests and browser tests must run only through Aspire-managed AppHost orchestration.
77+
- Keep infrastructure required by integration or browser coverage in Aspire projects/resources instead of ad hoc test startup code.

.codex/skills/mcaf-testing/SKILL.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@ compatibility: "Requires the repository’s build and test tooling; uses command
4747
- `mcaf-dotnet-xunit`
4848
- `mcaf-dotnet-tunit`
4949
- `mcaf-dotnet-mstest`
50-
5. Prefer integration, API, or UI coverage when behaviour crosses boundaries.
51-
6. Prove the user flow or caller-visible system flow, not just internal details.
52-
7. Add a regression test for every bug that can be captured reliably.
53-
8. If the stack is .NET and production code changed, do not stop at tests only. Finish with the repo-defined format and analyzer pass as well.
54-
9. Use deeper testing references only when the repo’s current strategy is unclear.
50+
5. When installing or validating MCAF/.NET test setup, require Aspire CLI for integration, browser, hosted, or infrastructure-backed tests. Install with `curl -sSL https://aspire.dev/install.sh | bash` on macOS/Linux and validate with `aspire --version`.
51+
6. Prefer Aspire-backed integration, API, or UI coverage when behaviour crosses boundaries.
52+
7. Treat Stub, Fake, and Mock doubles as forbidden by default. Use real implementations, Aspire-managed resources, public APIs, or user-visible flows instead.
53+
8. Allow a Stub, Fake, or Mock only when the real dependency cannot practically run through Aspire or when an interaction has no observable state or output. Document the reason and removal plan.
54+
9. Prove the user flow or caller-visible system flow, not just internal details.
55+
10. Add a regression test for every bug that can be captured reliably.
56+
11. If the stack is .NET and production code changed, do not stop at tests only. Finish with the repo-defined format and analyzer pass as well.
57+
12. Use deeper testing references only when the repo’s current strategy is unclear.
5558

5659
## Deliver
5760

@@ -63,6 +66,8 @@ compatibility: "Requires the repository’s build and test tooling; uses command
6366
- the new behaviour is covered at the right level
6467
- the main user flow or caller-visible system flow is proven
6568
- tests assert meaningful outcomes, not implementation trivia
69+
- Stub, Fake, and Mock doubles are absent, or every exception has a documented reason and removal plan
70+
- infrastructure-backed tests run through Aspire instead of ad hoc startup code
6671
- coverage expectations from `AGENTS.md` are met, or the exception is documented
6772
- the verification sequence matches `AGENTS.md`
6873
- for .NET changes, tests were not treated as a substitute for formatting or analyzer gates

.codex/skills/mcaf-testing/references/automated-testing.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Automated testing in MCAF is evidence, not ceremony.
88
- default to TDD: write the failing test first, make it pass, then refactor
99
- pick the highest-value test level that proves the change
1010
- tests prove user-visible or caller-visible flow, not just internal implementation
11+
- Stub, Fake, and Mock doubles are forbidden by default; avoid them unless an exception is explicitly documented with the reason and removal plan
12+
- use Aspire-managed resources for integration, browser, hosted, and infrastructure-backed tests
1113
- start small, then widen verification only when necessary
1214
- flaky tests are bugs
1315

@@ -20,15 +22,16 @@ Automated testing in MCAF is evidence, not ceremony.
2022
## Test-Level Selection
2123

2224
- unit tests for isolated logic
23-
- integration tests for real boundary interaction
25+
- integration tests for real boundary interaction through Aspire-managed orchestration when infrastructure or hosting is involved
2426
- API tests for public contracts
25-
- UI or end-to-end tests for user-visible flows
27+
- UI or end-to-end tests for user-visible flows through Aspire-managed application hosting
2628

2729
## User-Flow Coverage
2830

2931
- tests must prove the main user flow or caller-visible system flow changed by the work
3032
- for user-visible behaviour, cover the happy path and the most important failure or edge path at integration, API, or UI level
3133
- for cross-boundary changes, prefer tests that exercise the real contract between components over isolated implementation checks
34+
- prefer moving verification into Aspire whenever the behavior needs hosted services, browsers, containers, databases, queues, caches, or emulators
3235
- if the change is not directly user-facing, prove the system flow at the boundary that another module, job, or API caller actually uses
3336

3437
## Coverage Norms
@@ -48,7 +51,8 @@ Automated testing in MCAF is evidence, not ceremony.
4851

4952
## Common Smells
5053

51-
- mocks, fakes, or stubs hiding broken integration paths
54+
- any Stub, Fake, or Mock without a documented exception and removal plan
55+
- ad hoc infrastructure startup outside Aspire for integration or browser tests
5256
- huge suites run before any focused verification
5357
- tests that only prove implementation detail
5458
- tests nobody trusts enough to keep

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ List only the skills this repository should actively use.
171171
- Bug fixes start with a failing regression test when practical.
172172
- Test user-visible behavior and boundary contracts, not only internal implementation details.
173173
- Prefer realistic verification over mock-heavy tests.
174+
- Stub, Fake, and Mock test doubles are forbidden by default. Avoid them and prove behavior through real implementations, Aspire-managed resources, public APIs, or user-visible flows.
175+
- A Stub may only supply deterministic indirect input, a Fake may only replace a dependency that cannot practically run in Aspire, and a Mock may only verify an interaction that has no observable state or output. Every exception must be documented in the nearest test or durable doc with the reason and removal plan.
176+
- Aspire CLI is a required local prerequisite alongside the MCAF skill setup. Install it with `curl -sSL https://aspire.dev/install.sh | bash` on macOS/Linux and verify with `aspire --version`.
177+
- All repository tests must be able to run under Aspire orchestration when the test surface depends on application hosting, services, browsers, or external infrastructure.
178+
- Integration tests and browser tests must run only through Aspire-managed AppHost orchestration; do not create parallel ad hoc service startup, browser host, container, database, queue, cache, or emulator infrastructure outside Aspire for those tests.
179+
- Test infrastructure that supports integration or browser coverage belongs in Aspire projects and Aspire resources so local, CI, and future runtime verification use the same orchestration path. Prefer moving more verification into Aspire whenever infrastructure, hosting, or end-to-end behavior is involved.
174180
- Flaky tests are failures and must be fixed, not ignored.
175181
- Active runtime CI coverage gates must stay at or above 90% unless an ADR explicitly changes that policy.
176182
- Repository or module coverage must not go down without an explicit written exception.

SDK/dotnet/tests/ManagedCode.Tps.Tests/AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,6 @@ For this .NET project:
6565
- Keep test project namespaces under `ManagedCode.Tps.Tests`.
6666
- Do not move production logic into test helpers.
6767
- Prefer behavior-oriented tests over implementation-detail assertions.
68+
- Stub, Fake, and Mock doubles are forbidden by default; use real implementations and Aspire-managed resources unless an exception is documented with a reason and removal plan.
69+
- Keep integration and browser tests Aspire-owned: they must use the repository AppHost and Aspire-managed resources instead of starting local infrastructure directly from tests.
70+
- Keep any infrastructure required by integration or browser coverage in Aspire projects/resources so `aspire run` remains the canonical orchestration path.

0 commit comments

Comments
 (0)