diff --git a/Source/Mockolate/MockRegistry.Interactions.cs b/Source/Mockolate/MockRegistry.Interactions.cs
index 75492975..90b7c9b9 100644
--- a/Source/Mockolate/MockRegistry.Interactions.cs
+++ b/Source/Mockolate/MockRegistry.Interactions.cs
@@ -89,6 +89,28 @@ public void ClearAllInteractions()
return table[memberId];
}
+ ///
+ /// Returns the current default-scope property setup registered under the generator-emitted
+ /// , or when no setup has been registered.
+ ///
+ ///
+ /// Property dispatch reads the snapshot via
+ /// and falls back to the cold path when the snapshot is empty, so this accessor is intended for
+ /// diagnostics and tests that need to verify the fast-path table directly.
+ ///
+ /// The generator-emitted member id for the property accessor.
+ /// The setup for the property, or when none is registered.
+ internal PropertySetup? GetPropertySetupSnapshot(int memberId)
+ {
+ PropertySetup?[]? table = Volatile.Read(ref _propertySetupsByMemberId);
+ if (table is null || (uint)memberId >= (uint)table.Length)
+ {
+ return null;
+ }
+
+ return table[memberId];
+ }
+
///
/// Enumerates method setups of type matching
/// in latest-registered-first order, scenario-scoped setups before default-scope setups.
diff --git a/Source/Mockolate/MockRegistry.Setup.cs b/Source/Mockolate/MockRegistry.Setup.cs
index 704e7a8c..7b99aedd 100644
--- a/Source/Mockolate/MockRegistry.Setup.cs
+++ b/Source/Mockolate/MockRegistry.Setup.cs
@@ -77,8 +77,10 @@ private void AppendToIndexerMemberIdBucket(int memberId, IndexerSetup indexerSet
{
IndexerSetup[]?[]? oldTable = _indexerSetupsByMemberId;
int requiredLen = memberId + 1;
+ // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical.
int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen);
IndexerSetup[]?[] newTable;
+ // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable.
if (oldTable is null || newLen > oldTable.Length)
{
newTable = new IndexerSetup[newLen][];
@@ -168,8 +170,10 @@ private void AppendToMemberIdBucket(int memberId, MethodSetup methodSetup)
{
MethodSetup[]?[]? oldTable = _setupsByMemberId;
int requiredLen = memberId + 1;
+ // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical.
int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen);
MethodSetup[]?[] newTable;
+ // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable.
if (oldTable is null || newLen > oldTable.Length)
{
newTable = new MethodSetup[newLen][];
@@ -261,8 +265,10 @@ private void PublishPropertyToMemberIdBucket(int memberId, PropertySetup propert
{
PropertySetup?[]? oldTable = _propertySetupsByMemberId;
int requiredLen = memberId + 1;
+ // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical.
int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen);
PropertySetup?[] newTable;
+ // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable.
if (oldTable is null || newLen > oldTable.Length)
{
newTable = new PropertySetup?[newLen];
@@ -344,8 +350,10 @@ private void AppendToEventMemberIdBucket(int memberId, EventSetup eventSetup)
{
EventSetup[]?[]? oldTable = _eventSetupsByMemberId;
int requiredLen = memberId + 1;
+ // Stryker disable once Conditional : when oldTable is not null and requiredLen <= oldTable.Length, the resize-condition below is false either way and the else-branch reuses oldTable; newLen is unread, so collapsing the ternary to requiredLen is observationally identical.
int newLen = oldTable is null ? requiredLen : Math.Max(oldTable.Length, requiredLen);
EventSetup[]?[] newTable;
+ // Stryker disable once Equality : flipping > to >= only fires the realloc-and-copy path when newLen == oldTable.Length, producing a fresh table with identical contents — externally indistinguishable from reusing oldTable.
if (oldTable is null || newLen > oldTable.Length)
{
newTable = new EventSetup[newLen][];
diff --git a/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs b/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs
index b71ac00b..5051a7ff 100644
--- a/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs
+++ b/Tests/Mockolate.Internal.Tests/Registry/MockRegistrySetupSnapshotTests.cs
@@ -103,6 +103,36 @@ public async Task SetupMethod_WithSameMemberIdTwice_AppendsToBucket()
public sealed class PropertyTests
{
+ [Fact]
+ public async Task PublishPropertyToMemberIdBucket_WhenResized_PreservesEarlierEntries()
+ {
+ MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0));
+ PropertySetup setupLow = new(registry, "P5");
+ setupLow.InitializeWith(50);
+ PropertySetup setupHigh = new(registry, "P10");
+ setupHigh.InitializeWith(100);
+
+ registry.SetupProperty(5, setupLow);
+ registry.SetupProperty(10, setupHigh);
+
+ await That(registry.GetPropertySetupSnapshot(5)).IsSameAs(setupLow);
+ await That(registry.GetPropertySetupSnapshot(10)).IsSameAs(setupHigh);
+ }
+
+ [Fact]
+ public async Task PublishPropertyToMemberIdBucket_WithDefaultThenDefault_OverwritesInSnapshotTable()
+ {
+ MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0));
+ PropertySetup.Default defaultA = new("P1", 0);
+ PropertySetup.Default defaultB = new("P1", 0);
+
+ registry.SetupProperty(1, defaultA);
+ registry.SetupProperty(1, defaultB);
+
+ PropertySetup? snapshot = registry.GetPropertySetupSnapshot(1);
+ await That(snapshot).IsSameAs(defaultB);
+ }
+
[Fact]
public async Task PublishPropertyToMemberIdBucket_WithDefaultThenUserSetup_RetainsUserSetup()
{
@@ -119,6 +149,20 @@ public async Task PublishPropertyToMemberIdBucket_WithDefaultThenUserSetup_Retai
await That(observed).IsEqualTo(99);
}
+ [Fact]
+ public async Task PublishPropertyToMemberIdBucket_WithUserThenDefault_RetainsUserSetupInSnapshotTable()
+ {
+ MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0));
+ PropertySetup userSetup = new(registry, "P1");
+ userSetup.InitializeWith(99);
+
+ registry.SetupProperty(1, userSetup);
+ registry.SetupProperty(1, new PropertySetup.Default("P1", 0));
+
+ PropertySetup? snapshot = registry.GetPropertySetupSnapshot(1);
+ await That(snapshot).IsSameAs(userSetup);
+ }
+
[Fact]
public async Task PublishPropertyToMemberIdBucket_WithUserThenDefaultSetup_RetainsUserSetup()
{
@@ -166,6 +210,19 @@ public async Task SetupProperty_WithMemberId_PublishesToSnapshot()
await That(observed).IsEqualTo(50);
}
+ [Fact]
+ public async Task SetupProperty_WithMemberId_PublishesUserSetupToSnapshotTable()
+ {
+ MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0));
+ PropertySetup setup = new(registry, "P5");
+ setup.InitializeWith(50);
+
+ registry.SetupProperty(5, setup);
+
+ PropertySetup? snapshot = registry.GetPropertySetupSnapshot(5);
+ await That(snapshot).IsSameAs(setup);
+ }
+
[Fact]
public async Task SetupProperty_WithMemberIdAndDefaultScenario_PublishesToSnapshot()
{
@@ -179,6 +236,19 @@ public async Task SetupProperty_WithMemberIdAndDefaultScenario_PublishesToSnapsh
await That(observed).IsEqualTo(50);
}
+ [Fact]
+ public async Task SetupProperty_WithMemberIdAndDefaultScenario_PublishesUserSetupToSnapshotTable()
+ {
+ MockRegistry registry = new(MockBehavior.Default, new FastMockInteractions(0));
+ PropertySetup setup = new(registry, "P5");
+ setup.InitializeWith(50);
+
+ registry.SetupProperty(5, "", setup);
+
+ PropertySetup? snapshot = registry.GetPropertySetupSnapshot(5);
+ await That(snapshot).IsSameAs(setup);
+ }
+
[Fact]
public async Task SetupProperty_WithMemberIdAndNamedScenario_DoesNotPublishToSnapshot()
{