Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ step.
| `Arg.Compat.X<T>(...)` | same as the corresponding `Arg.X<T>(...)` |
| Nested mocks (`sub.Child.M(args).Returns(v)`) | `sub.Child.Mock.Setup.M(args).Returns(v)` plus a `// TODO` comment to register `Child` |

## CallInfo callbacks

NSubstitute's `Returns(call => …)`, `When(...).Do(call => …)`, and `AndDoes(call => …)` callbacks
receive a `CallInfo` parameter that exposes the invocation arguments. Mockolate's equivalent
overloads instead take the method's parameters directly, so the migration rewrites the lambda:

- **Body never reads the parameter** — drop it: `Do(call => x++)` → `Do(() => x++)`.
- **Body uses statically resolvable accesses** (`call.ArgAt<T>(literalIndex)`,
`call[literalIndex]`, or type-unique `call.Arg<T>()`) — rewrite the lambda's parameter list to
match the receiver method and replace each access with the matching parameter name:
`Returns(call => call.ArgAt<int>(0) + 1)` → `Returns((int x) => x + 1)`.
- **Anything else** — bare `call`, dynamic indices, indexer-write for out/ref, ambiguous
`call.Arg<T>()`, or local variables that would shadow the injected parameter names — preserve
the original lambda and emit a `// TODO` comment so the rewrite can be done by hand.

## Argument arity

Mockolate exposes both a direct-value overload and a matcher overload for properties and for methods with up to
Expand Down

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions Tests/Mockolate.Migration.MoqPlayground/ArgumentMatcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Mockolate.Migration.MoqPlayground;
public class ArgumentMatcherTests
{
[Fact]
public async Task ItIs_predicate_matchesEvenAmounts()
public async Task ItIs_Predicate_MatchesEvenAmounts()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense("Dark", It.Is<int>(i => i % 2 == 0))).Returns(true);
Expand All @@ -21,7 +21,7 @@ public async Task ItIs_predicate_matchesEvenAmounts()
}

[Fact]
public async Task ItIsAny_matchesAnyValue()
public async Task ItIsAny_MatchesAnyValue()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense(It.IsAny<string>(), It.IsAny<int>())).Returns(true);
Expand All @@ -31,7 +31,7 @@ public async Task ItIsAny_matchesAnyValue()
}

[Fact]
public async Task ItIsInRange_inclusive_matchesBoundaries()
public async Task ItIsInRange_Inclusive_MatchesBoundaries()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense("Dark", It.IsInRange(1, 5, Range.Inclusive))).Returns(true);
Expand All @@ -42,7 +42,7 @@ public async Task ItIsInRange_inclusive_matchesBoundaries()
}

[Fact]
public async Task ItIsNotNull_rejectsNull()
public async Task ItIsNotNull_RejectsNull()
{
Mock<IChocolateFactory> factory = new();
factory.Setup(f => f.RegisterRecipe(It.IsNotNull<string>())).Returns(true);
Expand All @@ -52,7 +52,7 @@ public async Task ItIsNotNull_rejectsNull()
}

[Fact]
public async Task ItIsRegex_matchesPattern()
public async Task ItIsRegex_MatchesPattern()
{
Mock<IChocolateFactory> factory = new();
factory.Setup(f => f.RegisterRecipe(It.IsRegex("^Dark", RegexOptions.IgnoreCase))).Returns(true);
Expand All @@ -62,7 +62,7 @@ public async Task ItIsRegex_matchesPattern()
}

[Fact]
public async Task OutParameter_isSetByItIsOut()
public async Task OutParameter_IsSetByItIsOut()
{
Mock<IChocolateDispenser> dispenser = new();
int reserved = 7;
Expand All @@ -75,7 +75,7 @@ public async Task OutParameter_isSetByItIsOut()
}

[Fact]
public async Task PlainValue_isUsedAsExactMatch()
public async Task PlainValue_IsUsedAsExactMatch()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense("Milk", 3)).Returns(true);
Expand All @@ -85,7 +85,7 @@ public async Task PlainValue_isUsedAsExactMatch()
}

[Fact]
public async Task RefParameter_anyMatch_acceptsAnyRef()
public async Task RefParameter_AnyMatch_AcceptsAnyRef()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Refill("Dark", ref It.Ref<int>.IsAny)).Returns(true);
Expand Down
10 changes: 5 additions & 5 deletions Tests/Mockolate.Migration.MoqPlayground/CreationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Mockolate.Migration.MoqPlayground;
public class CreationTests
{
[Fact]
public async Task ClassMockWithConstructorArgs_isCreatedWithThoseArgs()
public async Task ClassMockWithConstructorArgs_IsCreatedWithThoseArgs()
{
// ChocolateRecipe has a parameterless ctor, but Moq supports passing args to base.
Mock<ChocolateRecipe> recipe = new();
Expand All @@ -19,7 +19,7 @@ public async Task ClassMockWithConstructorArgs_isCreatedWithThoseArgs()
}

[Fact]
public async Task DefaultLooseMock_returnsDefaultsForUnsetMembers()
public async Task DefaultLooseMock_ReturnsDefaultsForUnsetMembers()
{
Mock<IChocolateDispenser> dispenser = new();

Expand All @@ -30,15 +30,15 @@ public async Task DefaultLooseMock_returnsDefaultsForUnsetMembers()
}

[Fact]
public async Task ExplicitLooseMock_isEquivalentToDefault()
public async Task ExplicitLooseMock_IsEquivalentToDefault()
{
Mock<IChocolateDispenser> dispenser = new(MockBehavior.Loose);

await That(dispenser.Object.Dispense("Dark", 1)).IsFalse();
}

[Fact]
public async Task ObjectAccess_isUsedToReachTheMockedInstance()
public async Task ObjectAccess_IsUsedToReachTheMockedInstance()
{
Mock<IChocolateFactory> factory = new();
factory.Setup(f => f.RegisterRecipe("Truffle")).Returns(true);
Expand All @@ -49,7 +49,7 @@ public async Task ObjectAccess_isUsedToReachTheMockedInstance()
}

[Fact]
public async Task StrictMock_throwsForUnsetMembers()
public async Task StrictMock_ThrowsForUnsetMembers()
{
Mock<IChocolateDispenser> dispenser = new(MockBehavior.Strict);

Expand Down
10 changes: 5 additions & 5 deletions Tests/Mockolate.Migration.MoqPlayground/EventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Mockolate.Migration.MoqPlayground;
public class EventTests
{
[Fact]
public async Task Raise_customDelegate_invokesSubscribedHandler()
public async Task Raise_CustomDelegate_InvokesSubscribedHandler()
{
Mock<IChocolateDispenser> dispenser = new();
string? observedType = null;
Expand All @@ -27,7 +27,7 @@ public async Task Raise_customDelegate_invokesSubscribedHandler()
}

[Fact]
public async Task Raise_eventHandlerStandard_passesArgs()
public async Task Raise_EventHandlerStandard_PassesArgs()
{
Mock<IChocolateDispenser> dispenser = new();
int? observed = null;
Expand All @@ -39,7 +39,7 @@ public async Task Raise_eventHandlerStandard_passesArgs()
}

[Fact]
public async Task ShopSubscribesOnConstruction_andTracksDispensedAmounts()
public async Task ShopSubscribesOnConstruction_AndTracksDispensedAmounts()
{
Mock<IChocolateDispenser> dispenser = new();
Mock<IChocolateFactory> factory = new();
Expand All @@ -58,7 +58,7 @@ public async Task ShopSubscribesOnConstruction_andTracksDispensedAmounts()
}

[Fact]
public async Task VerifyAdd_recordsSubscription()
public async Task VerifyAdd_RecordsSubscription()
{
Mock<IChocolateDispenser> dispenser = new();
ChocolateDispensedDelegate handler = (_, _) => { };
Expand All @@ -68,7 +68,7 @@ public async Task VerifyAdd_recordsSubscription()
}

[Fact]
public async Task VerifyRemove_recordsUnsubscription()
public async Task VerifyRemove_RecordsUnsubscription()
{
Mock<IChocolateDispenser> dispenser = new();
ChocolateDispensedDelegate handler = (_, _) => { };
Expand Down
4 changes: 2 additions & 2 deletions Tests/Mockolate.Migration.MoqPlayground/SequenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Mockolate.Migration.MoqPlayground;
public class SequenceTests
{
[Fact]
public async Task MockSequence_strictOrdering_isHonored()
public async Task MockSequence_StrictOrdering_IsHonored()
{
Mock<IChocolateDispenser> dispenser = new(MockBehavior.Strict);
MockSequence sequence = new();
Expand All @@ -25,7 +25,7 @@ public async Task MockSequence_strictOrdering_isHonored()
}

[Fact]
public async Task SetupSequence_returnsValuesInOrder()
public async Task SetupSequence_ReturnsValuesInOrder()
{
Mock<IChocolateFactory> factory = new();
factory.SetupSequence(f => f.RegisterRecipe(It.IsAny<string>()))
Expand Down
28 changes: 14 additions & 14 deletions Tests/Mockolate.Migration.MoqPlayground/SetupTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Mockolate.Migration.MoqPlayground;
public class SetupTests
{
[Fact]
public async Task Callback_observesArgumentsBeforeReturning()
public async Task Callback_ObservesArgumentsBeforeReturning()
{
Mock<IChocolateDispenser> dispenser = new();
string? observedType = null;
Expand All @@ -30,7 +30,7 @@ public async Task Callback_observesArgumentsBeforeReturning()
}

[Fact]
public async Task NestedMockSetup_recursive_chainsThroughChildMock()
public async Task NestedMockSetup_Recursive_ChainsThroughChildMock()
{
// Auto-mocking hierarchy: Moq creates child mocks for properties on demand.
Mock<INested> outer = new()
Expand All @@ -43,7 +43,7 @@ public async Task NestedMockSetup_recursive_chainsThroughChildMock()
}

[Fact]
public async Task Returns_argumentBased_evaluatesFromArgument()
public async Task Returns_ArgumentBased_EvaluatesFromArgument()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense(It.IsAny<string>())).Returns((string s) => s.Length > 0);
Expand All @@ -53,7 +53,7 @@ public async Task Returns_argumentBased_evaluatesFromArgument()
}

[Fact]
public async Task Returns_directValue_dispensesAndShopRecordsTotal()
public async Task Returns_DirectValue_DispensesAndShopRecordsTotal()
{
Mock<IChocolateDispenser> dispenser = new();
Mock<IChocolateFactory> factory = new();
Expand All @@ -68,7 +68,7 @@ public async Task Returns_directValue_dispensesAndShopRecordsTotal()
}

[Fact]
public async Task Returns_lazyFactory_evaluatedOnEachCall()
public async Task Returns_LazyFactory_EvaluatedOnEachCall()
{
Mock<IChocolateDispenser> dispenser = new();
int count = 1;
Expand All @@ -83,7 +83,7 @@ public async Task Returns_lazyFactory_evaluatedOnEachCall()
}

[Fact]
public async Task ReturnsAsync_completesWithValue()
public async Task ReturnsAsync_CompletesWithValue()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.DispenseAsync("Dark", 1)).ReturnsAsync(true);
Expand All @@ -92,7 +92,7 @@ public async Task ReturnsAsync_completesWithValue()
}

[Fact]
public async Task ReturnsAsync_factory_evaluatesPerCall()
public async Task ReturnsAsync_Factory_EvaluatesPerCall()
{
Mock<IChocolateFactory> factory = new();
factory.Setup(f => f.BakeAsync(It.IsAny<string>(), It.IsAny<int>()))
Expand All @@ -106,7 +106,7 @@ public async Task ReturnsAsync_factory_evaluatesPerCall()
}

[Fact]
public async Task SetupGet_property_returnsConfiguredValue()
public async Task SetupGet_Property_ReturnsConfiguredValue()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.SetupGet(d => d.Name).Returns("Choco-9000");
Expand All @@ -115,7 +115,7 @@ public async Task SetupGet_property_returnsConfiguredValue()
}

[Fact]
public async Task SetupProperty_tracksAssignmentsAndReturnsLastValue()
public async Task SetupProperty_TracksAssignmentsAndReturnsLastValue()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.SetupProperty(d => d.Name);
Expand All @@ -126,7 +126,7 @@ public async Task SetupProperty_tracksAssignmentsAndReturnsLastValue()
}

[Fact]
public async Task SetupProperty_withInitialValue_returnsThatBeforeAssignment()
public async Task SetupProperty_WithInitialValue_ReturnsThatBeforeAssignment()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.SetupProperty(d => d.Name, "Default");
Expand All @@ -135,7 +135,7 @@ public async Task SetupProperty_withInitialValue_returnsThatBeforeAssignment()
}

[Fact]
public async Task SetupSequence_returnsValuesInOrder_thenDefault()
public async Task SetupSequence_ReturnsValuesInOrder_ThenDefault()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.SetupSequence(d => d.Dispense(It.IsAny<string>(), It.IsAny<int>()))
Expand All @@ -150,7 +150,7 @@ await That(() => dispenser.Object.Dispense("Dark", 1))
}

[Fact]
public async Task Throws_genericException_isRaisedOnInvocation()
public async Task Throws_GenericException_IsRaisedOnInvocation()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense("reset", 0)).Throws<InvalidOperationException>();
Expand All @@ -160,7 +160,7 @@ await That(() => dispenser.Object.Dispense("reset", 0))
}

[Fact]
public async Task Throws_specificInstance_isRaisedOnInvocation()
public async Task Throws_SpecificInstance_IsRaisedOnInvocation()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.Dispense("", 0)).Throws(new InvalidChocolateException("empty type"));
Expand All @@ -171,7 +171,7 @@ await That(() => dispenser.Object.Dispense("", 0))
}

[Fact]
public async Task ThrowsAsync_completesWithException()
public async Task ThrowsAsync_CompletesWithException()
{
Mock<IChocolateDispenser> dispenser = new();
dispenser.Setup(d => d.DispenseAsync("Dark", 1)).ThrowsAsync(new TimeoutException());
Expand Down
Loading
Loading