mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
948a9a52ef
* Add exp-mock-usage-analysis skill and evaluation New experimental skill that detects unused, redundant, or unnecessary mocks in .NET test suites. Covers Moq, NSubstitute, FakeItEasy, and manual test doubles. - SKILL.md with 6-step workflow: gather code, classify mocks, detect anti-patterns, incorporate runtime data, generate recommendations, report findings - eval.yaml with 10 scenarios: trivial type mocking, unused setups, redundant configs, stable utility mocks, well-placed mocks (no false positives), NSubstitute support, FakeItEasy support, setup sprawl, and 2 non-activation boundary tests - Fixture files for each scenario with production + test code - CODEOWNERS entry for @dotnet/dotnet-testing * Remove no-headroom scenarios, increase NSubstitute timeout Remove 4 scenarios where baseline already scores 5.0/5 (model handles these well without skill guidance): - Detect mocking of DTOs, records, and enums - Recognize well-placed mocks without inventing false positives - Decline when asked about non-mock test anti-patterns - Decline mock framework migration request Increase NSubstitute scenario timeout from 120s to 180s to avoid baseline timeouts impacting scoring. Remaining 6 scenarios all show positive skill impact (3→5, 3→4, etc). * Document areas the model handles well without the skill Add a subsection under 'When Not to Use' listing mock-related tasks where the base model already scores 5.0/5 without skill guidance: - Mocking trivial types (DTOs, records, enums) - Recognizing well-placed mocks (no false positives) * Fix md * Improve
103 lines
4.3 KiB
C#
103 lines
4.3 KiB
C#
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Moq;
|
|
using InventorySystem;
|
|
|
|
namespace InventorySystem.Tests;
|
|
|
|
[TestClass]
|
|
public sealed class InventoryServiceTests
|
|
{
|
|
private Mock<IInventoryRepository> _mockRepo = null!;
|
|
private Mock<IAuditLogger> _mockAudit = null!;
|
|
private Mock<IEventBus> _mockEventBus = null!;
|
|
private InventoryService _service = null!;
|
|
|
|
[TestInitialize]
|
|
public void Setup()
|
|
{
|
|
_mockRepo = new Mock<IInventoryRepository>();
|
|
_mockAudit = new Mock<IAuditLogger>();
|
|
_mockEventBus = new Mock<IEventBus>();
|
|
_service = new InventoryService(_mockRepo.Object, _mockAudit.Object, _mockEventBus.Object);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Reserve_SufficientStock_ReturnsTrue()
|
|
{
|
|
_mockRepo.Setup(r => r.GetStock("PROD-1")).Returns(50);
|
|
_mockRepo.Setup(r => r.UpdateStock(It.IsAny<string>(), It.IsAny<int>()));
|
|
// Unused setup — GetLowStockProducts is never called during Reserve
|
|
_mockRepo.Setup(r => r.GetLowStockProducts(It.IsAny<int>())).Returns(new List<string> { "PROD-1" });
|
|
_mockAudit.Setup(a => a.LogStockChange(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>()));
|
|
_mockEventBus.Setup(e => e.Publish(It.IsAny<string>(), It.IsAny<object>()));
|
|
|
|
var result = _service.Reserve("PROD-1", 10);
|
|
|
|
Assert.IsTrue(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Reserve_InsufficientStock_ReturnsFalse()
|
|
{
|
|
_mockRepo.Setup(r => r.GetStock("PROD-2")).Returns(5);
|
|
// These setups are unnecessary — when stock is insufficient, UpdateStock is never called
|
|
_mockRepo.Setup(r => r.UpdateStock(It.IsAny<string>(), It.IsAny<int>()));
|
|
_mockAudit.Setup(a => a.LogStockChange(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>()));
|
|
_mockEventBus.Setup(e => e.Publish(It.IsAny<string>(), It.IsAny<object>()));
|
|
// Also unused
|
|
_mockRepo.Setup(r => r.GetLowStockProducts(10)).Returns(new List<string>());
|
|
|
|
var result = _service.Reserve("PROD-2", 10);
|
|
|
|
Assert.IsFalse(result);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Restock_ValidQuantity_UpdatesStock()
|
|
{
|
|
_mockRepo.Setup(r => r.GetStock("PROD-3")).Returns(20);
|
|
_mockRepo.Setup(r => r.UpdateStock("PROD-3", 30));
|
|
_mockAudit.Setup(a => a.LogStockChange("PROD-3", 20, 30, "Restocked"));
|
|
// Unused: EventBus.Publish is never called during Restock
|
|
_mockEventBus.Setup(e => e.Publish("StockRestocked", It.IsAny<object>()));
|
|
// Unused: GetLowStockProducts never called
|
|
_mockRepo.Setup(r => r.GetLowStockProducts(It.IsAny<int>())).Returns(new List<string>());
|
|
|
|
_service.Restock("PROD-3", 10);
|
|
|
|
_mockRepo.Verify(r => r.UpdateStock("PROD-3", 30), Times.Once);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Restock_ZeroQuantity_Throws()
|
|
{
|
|
// All these setups are unnecessary — the method throws before using any dependency
|
|
_mockRepo.Setup(r => r.GetStock(It.IsAny<string>())).Returns(100);
|
|
_mockRepo.Setup(r => r.UpdateStock(It.IsAny<string>(), It.IsAny<int>()));
|
|
_mockAudit.Setup(a => a.LogStockChange(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<string>()));
|
|
_mockEventBus.Setup(e => e.Publish(It.IsAny<string>(), It.IsAny<object>()));
|
|
|
|
Assert.ThrowsException<ArgumentOutOfRangeException>(() => _service.Restock("PROD-4", 0));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Reserve_VerifiesExactCallSequence()
|
|
{
|
|
_mockRepo.Setup(r => r.GetStock("PROD-5")).Returns(100);
|
|
_mockRepo.Setup(r => r.UpdateStock("PROD-5", 95));
|
|
_mockAudit.Setup(a => a.LogStockChange("PROD-5", 100, 95, "Reserved"));
|
|
_mockEventBus.Setup(e => e.Publish("StockReserved", It.IsAny<object>()));
|
|
|
|
_service.Reserve("PROD-5", 5);
|
|
|
|
// Over-verification: testing exact call sequence instead of outcomes
|
|
_mockRepo.Verify(r => r.GetStock("PROD-5"), Times.Once);
|
|
_mockRepo.Verify(r => r.UpdateStock("PROD-5", 95), Times.Once);
|
|
_mockAudit.Verify(a => a.LogStockChange("PROD-5", 100, 95, "Reserved"), Times.Once);
|
|
_mockEventBus.Verify(e => e.Publish("StockReserved", It.IsAny<object>()), Times.Once);
|
|
_mockRepo.VerifyNoOtherCalls();
|
|
_mockAudit.VerifyNoOtherCalls();
|
|
_mockEventBus.VerifyNoOtherCalls();
|
|
}
|
|
}
|