@@ -122,8 +122,15 @@ 2. You may only create or update the test file
122122 Domain services:
123123 - Coordinate business logic that doesn't naturally fit within a single aggregate
124124 - Work with repositories to load and manipulate domain entities
125- - May call methods on domain entities to maintain domain invariants
125+ - Call methods on domain entities to maintain domain invariants and modify their state
126126 - Should maintain transactional consistency through Unit of Work
127+
128+ **CRITICAL for Testing**: Domain services orchestrate behavior by calling entity methods. Your tests MUST verify:
129+ 1. The service loaded the correct entities (repository interaction)
130+ 2. The entities' STATE changed correctly after calling their methods (property/collection assertions)
131+ 3. The changes will be persisted (SaveChangesAsync called if needed)
132+
133+ Verifying ONLY repository calls without inspecting entity state is an INCOMPLETE test.
127134
128135 ## Test Coverage Requirements
129136 Generate tests that cover these scenarios (where applicable to the domain service operation):
@@ -134,7 +141,11 @@ Generate tests that cover these scenarios (where applicable to the domain servic
134141 * Boundary values for value objects (e.g., zero amounts, min/max dates)
135142 * Null or invalid input parameters
136143 * Entity state preconditions (e.g., entity must be in certain state before operation)
137- 5. **Entity State Changes**: Verify that entities are modified correctly through their methods
144+ 5. **Entity State Changes (CRITICAL)**:
145+ * ALWAYS verify that domain entities' state was actually modified by inspecting their properties/collections after the operation
146+ * Use Callback to capture entities OR directly inspect the entity reference passed to the service
147+ * Example: After calling `service.RevalueAsset(...)`, assert that `asset.CurrentValuation == expectedValue`
148+ * Don't just verify repository methods were called - verify the entity's state changed correctly
138149 6. **Repository Interactions**: Verify correct repository method calls (Find, Add, Update, Remove)
139150 7. **Unit of Work**: Verify SaveChangesAsync is called when state changes occur
140151
@@ -292,24 +303,29 @@ public async Task RevalueAssetAsync_UpdatesAssetValuation_WhenClientAndAssetExis
292303 AppraiserName = "John Appraiser"
293304 };
294305
295- // Create minimal test entity - only set what's needed
306+ // CRITICAL: Create test entity and ensure we can verify state changes
296307 var client = new Client { Id = clientId };
308+ var asset = new Asset { Id = assetId, CurrentValuation = 100000m };
309+ client.AddAsset(asset); // Set up initial state
297310
298311 _clientRepositoryMock
299312 .Setup(x => x.FindByIdAsync(clientId, It.IsAny<CancellationToken>()))
300313 .ReturnsAsync(client);
301314
302315 // Act
303- await _sut .RevalueAssetAsync(clientId, assetId, newValuation);
316+ await _service .RevalueAssetAsync(clientId, assetId, newValuation);
304317
305- // Assert - Verify repository was called and entity method was invoked
318+ // Assert - CRITICAL: Verify domain entity STATE was actually modified
306319 _clientRepositoryMock.Verify(
307320 x => x.FindByIdAsync(clientId, It.IsAny<CancellationToken>()),
308321 Times.Once);
309322
310- // Note: If Client.UpdateAssetValuation modifies state, verify that state here
311- // The Unit of Work will save changes, so no explicit SaveChanges verification needed
312- // unless the domain service explicitly calls it
323+ // Verify the asset's valuation was updated through the client aggregate
324+ var updatedAsset = client.Assets.FirstOrDefault(a => a.Id == assetId);
325+ Assert.NotNull(updatedAsset);
326+ Assert.Equal(250000m, updatedAsset.CurrentValuation);
327+ Assert.Equal(newValuation.ValuationDate, updatedAsset.ValuationDate);
328+ Assert.Equal(newValuation.AppraiserName, updatedAsset.AppraiserName);
313329 }
314330
315331 [Fact]
@@ -401,9 +417,11 @@ public async Task TransferAssetAsync_TransfersAsset_WhenBothClientsExist()
401417 var toClientId = Guid.NewGuid();
402418 var assetId = Guid.NewGuid();
403419
420+ var asset = new Asset { Id = assetId, ClientId = fromClientId, Value = 500000m };
404421 var fromClient = new Client { Id = fromClientId, IsActive = true };
422+ fromClient.AddAsset(asset); // Establish ownership
423+
405424 var toClient = new Client { Id = toClientId, IsActive = true };
406- var asset = new Asset { Id = assetId, ClientId = fromClientId };
407425
408426 _clientRepositoryMock
409427 .Setup(x => x.FindByIdAsync(fromClientId, It.IsAny<CancellationToken>()))
@@ -418,10 +436,17 @@ public async Task TransferAssetAsync_TransfersAsset_WhenBothClientsExist()
418436 .ReturnsAsync(asset);
419437
420438 // Act
421- await _sut .TransferAssetAsync(fromClientId, toClientId, assetId);
439+ await _service .TransferAssetAsync(fromClientId, toClientId, assetId);
422440
423- // Assert
441+ // Assert - Verify BOTH domain state changes AND repository interactions
442+ // CRITICAL: Verify the asset ownership actually changed
424443 Assert.Equal(toClientId, asset.ClientId);
444+
445+ // Verify asset was removed from source client and added to destination
446+ Assert.DoesNotContain(asset, fromClient.Assets);
447+ Assert.Contains(asset, toClient.Assets);
448+
449+ // Repository verifications (secondary to state assertions)
425450 _clientRepositoryMock.Verify(
426451 x => x.FindByIdAsync(fromClientId, It.IsAny<CancellationToken>()),
427452 Times.Once);
@@ -523,7 +548,12 @@ public async Task CalculateCommissionAsync_AppliesCap_ForHighValueTransaction()
523548 ## Critical Domain Service Testing Reminders
524549 - **Mock ALL repositories**: Domain services should not touch real databases
525550 - **Focus on coordination logic**: Test how the service orchestrates between entities and repositories
526- - **Verify entity method calls**: Use Callback to capture entities and verify their state changes
551+ - **ALWAYS verify domain entity state changes (HIGHEST PRIORITY)**:
552+ * Domain services coordinate behavior across aggregates by calling methods on domain entities
553+ * After the service executes, ALWAYS inspect the entity's properties/collections to verify state changed correctly
554+ * Example: If service calls `client.UpdateAssetValuation(...)`, assert `client.Assets[x].CurrentValuation == expectedValue`
555+ * Repository verification alone (e.g., `Verify(x => x.FindByIdAsync(...))`) is INSUFFICIENT - you must prove the entity's state changed
556+ * Use the entity reference returned from repository mock to inspect its final state in assertions
527557 - **Test business rule enforcement**: Ensure domain invariants are maintained
528558 - **Test all exception paths**: Domain services often have multiple failure scenarios
529559 - **Minimal test data**: Only set properties relevant to the specific test scenario
0 commit comments