diff --git a/docs/docs/migration/mocking/fakeiteasy.md b/docs/docs/migration/mocking/fakeiteasy.md new file mode 100644 index 00000000000..0ae84d3fea4 --- /dev/null +++ b/docs/docs/migration/mocking/fakeiteasy.md @@ -0,0 +1,482 @@ +--- +sidebar_label: FakeItEasy +--- + +# Migrating from FakeItEasy to TUnit.Mocks + +TUnit.Mocks generates mocks at compile time instead of creating runtime proxies. FakeItEasy configures a fake through `A.CallTo(() => fake.Member(...))`. TUnit.Mocks generates a strongly typed member on the mock for each member of the mocked type, so you call `mock.Member(...)` directly. The chained method decides the meaning: `.Returns()` configures a setup, and `.WasCalled()` verifies calls. + +TUnit.Mocks works with any test framework. You can migrate mocks before, after, or without migrating the tests to TUnit. + +## Before You Start + +1. Add the package: + + ```bash + dotnet add package TUnit.Mocks + ``` + +2. Set `14` (or `preview`) in the test project. TUnit.Mocks requires C# 14. Older versions report error `TM004`. +3. Read [Running Both Libraries Side by Side](#running-both-libraries-side-by-side) if you migrate one file at a time. `Times` exists in both libraries. + +## Quick Reference + +| FakeItEasy | TUnit.Mocks | +|---|---| +| `A.Fake()` | `IService.Mock()` | +| `A.Fake(o => o.Strict())` | `IService.Mock(MockBehavior.Strict)` | +| `A.Fake(o => o.WithArgumentsForConstructor(...))` | `MyClass.Mock(arg1, arg2)` | +| `A.Fake(o => o.Implements())` | `Mock.Of()` | +| `A.Fake(o => o.Wrapping(real))` | `Mock.Wrap(real)` (non-sealed classes only; no equivalent for interfaces) | +| The fake passed to the code under test | `mock` (converts implicitly) or `mock.Object` | +| `A.CallTo(() => fake.Method(1)).Returns(value)` | `mock.Method(1).Returns(value)` | +| `.ReturnsLazily((int id) => ...)` | `.Returns((int id) => ...)` | +| `.ReturnsNextFromSequence(a, b)` | `.ReturnsSequentially(a, b)` | +| `.Returns(a).Once().Then.Returns(b)` | `.Returns(a).Then().Returns(b)` | +| `.Throws()` / `.ThrowsAsync(ex)` | `.Throws()` / `.Throws(ex)` | +| `.Invokes((int id) => ...)` | `.Callback((int id) => ...)` | +| `.DoesNothing()` | `mock.VoidMethod(...)` with no chained behavior | +| `.CallsBaseMethod()` | Default for class mocks | +| `.AssignsOutAndRefParameters(value)` | `.SetsOut{ParameterName}(value)` | +| `A.Ignored` / `A._` | `Any()` or `Any()` | +| `A.That.Matches(x => ...)` | `x => ...` or `Is(x => ...)` | +| `.WithAnyArguments()` | `Any()` for each parameter (see [Argument Constraints](#argument-constraints)) | +| `.MustHaveHappened()` | `.WasCalled()` | +| `.MustHaveHappenedOnceExactly()` | `.WasCalled(Times.Once)` | +| `.MustNotHaveHappened()` | `.WasNeverCalled()` | +| `A.CallToSet(() => fake.Prop).To(value)` | `mock.Prop.Set(value)` | +| `fake.Event += Raise.With(args)` | `mock.Raise{EventName}(args)` | +| `Fake.GetCalls(fake)` | `mock.Invocations` | +| `Fake.ClearRecordedCalls(fake)` | `mock.Reset()` (also clears setups) | + + + +## Example Types + +The examples on this page use these types: + +```csharp +public record User(int Id, string Name); + +public sealed class UserSavedEventArgs(User user) : EventArgs +{ + public User User { get; } = user; +} + +public interface IUserRepository +{ + User? GetById(int id); + Task GetByIdAsync(int id); + void Save(User user); + bool TryGetName(int id, out string name); + string ConnectionName { get; set; } + event EventHandler? UserSaved; +} + +public abstract class PriceCalculator +{ + public virtual decimal Tax(decimal amount) => amount * 0.2m; + public abstract decimal Discount(decimal amount); +} +``` + +## Creating Mocks + +FakeItEasy returns the fake as the interface type. TUnit.Mocks returns a `Mock` wrapper. For interfaces, the wrapper also implements the interface, so you can pass it directly to the code under test. Use `.Object` when you need the `T` instance explicitly. + +```csharp +// FakeItEasy +var repository = A.Fake(); +IUserRepository sut = repository; +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +IUserRepository sut = repository; // implicit conversion +IUserRepository same = repository.Object; // explicit instance +``` + +`T.Mock()` requires C# 14. You can also use the factory form `Mock.Of()`. + +Both libraries are loose by default. In TUnit.Mocks, a strict mock throws `MockStrictBehaviorException` for unconfigured calls. + +```csharp +// FakeItEasy +var repository = A.Fake(options => options.Strict()); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(MockBehavior.Strict); +``` + +## Return Values + +Remove `A.CallTo(() => ...)` and call the member on the mock. + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.GetById(1)).Returns(new User(1, "Alice")); +A.CallTo(() => repository.GetById(A.Ignored)).Returns(new User(0, "Anyone")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1).Returns(new User(1, "Alice")); +repository.GetById(Any()).Returns(new User(0, "Anyone")); +``` + +In both libraries, the most recently added matching setup wins. + +### Computed Return Values + +`ReturnsLazily` maps to `Returns` with a lambda. The parameters are the method's arguments. + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.GetById(A._)).ReturnsLazily((int id) => new User(id, "Generated")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(Any()).Returns((int id) => new User(id, "Generated")); +``` + +### Async Methods + +FakeItEasy accepts either a value or a task for async members. In TUnit.Mocks, `Returns` wraps the value in `Task` or `ValueTask` for you. + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.GetByIdAsync(1)).Returns(new User(1, "Alice")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetByIdAsync(1).Returns(new User(1, "Alice")); +``` + +To return a task you create yourself, such as one from a `TaskCompletionSource`, use `ReturnsAsync(task)`. + +### Sequences + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.GetById(1)) + .Throws().Once() + .Then.ReturnsNextFromSequence(new User(1, "First"), new User(1, "Second")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1) + .Throws() + .Then() + .ReturnsSequentially(new User(1, "First"), new User(1, "Second")); +``` + +When a FakeItEasy sequence ends, later calls fall back to the fake's default behavior. In TUnit.Mocks, the last behavior repeats. In this example, every call after the third call returns `"Second"`. + +FakeItEasy limits a behavior with `.Once()`, `.Twice()`, or `.NumberOfTimes(n)`. TUnit.Mocks has no repeat count: each `.Then()` step applies to one call. Without `.Then()`, chained behaviors apply to the same call. + +## Exceptions + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.GetById(-1)).Throws(new ArgumentOutOfRangeException("id")); +A.CallTo(() => repository.GetByIdAsync(-1)).ThrowsAsync(new InvalidOperationException()); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(-1).Throws(new ArgumentOutOfRangeException("id")); +repository.GetByIdAsync(-1).Throws(); +``` + +TUnit.Mocks has no `ThrowsAsync`. For a method that returns `Task` or `ValueTask`, `Throws` returns a faulted task. It does not throw synchronously. + +## Callbacks and Void Methods + +`Invokes` maps to `Callback`. Declare the lambda parameter types to receive the method's arguments. + +```csharp +// FakeItEasy +var repository = A.Fake(); +var saved = new List(); +A.CallTo(() => repository.Save(A._)).Invokes((User user) => saved.Add(user)); +A.CallTo(() => repository.Save(A.That.Matches(u => u.Id < 0))).Throws(new ArgumentException("Invalid id")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +var saved = new List(); +repository.Save(Any()).Callback((User user) => saved.Add(user)); +repository.Save(user => user.Id < 0).Throws(new ArgumentException("Invalid id")); +``` + +`DoesNothing()` has no direct equivalent. In strict mode, a void setup with no chained behavior allows the call: `repository.Save(Any());`. + +## Argument Constraints + +TUnit.Mocks imports its matchers globally. A raw value is an exact match, and a lambda is a predicate. + +| FakeItEasy | TUnit.Mocks | +|---|---| +| `A.Ignored` / `A._` | `Any()` or `Any()` | +| `5` | `5` or `Is(5)` | +| `A.That.Matches(x => x > 0)` | `x => x > 0` or `Is(x => x > 0)` | +| `A.That.IsEqualTo(5)` | `5` or `Is(5)` | +| `A.That.IsNull()` | `IsNull()` | +| `A.That.IsNotNull()` | `IsNotNull()` | +| `A.That.Not.IsEqualTo(0)` | `Not(Is(0))` | +| `A>.That.Contains(5)` | `Contains, int>(5)` | +| `A>.That.IsEmpty()` | `IsEmpty>()` | +| `A>.That.IsSameSequenceAs(expected)` | `SequenceEquals, int>(expected)` | +| `A.That.StartsWith("a")` | `s => s.StartsWith("a")` | +| `.WithAnyArguments()` | `Any()` for each parameter, or `AnyArgs()` where generated | +| `.WhenArgumentsMatch(args => ...)` | A lambda matcher on each parameter | + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.GetById(A.That.Matches(id => id > 100))).Returns(new User(101, "Admin")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(id => id > 100).Returns(new User(101, "Admin")); +``` + +To replace `.WithAnyArguments()`: pass `Any()` for each parameter. Some methods also get an `AnyArgs()` shortcut that replaces all of them. It is not generated for overloaded or generic methods, for methods with fewer than two matchable parameters, or for methods with `out`, `ref`, or ref-struct parameters. See [when the shortcut is generated](../../writing-tests/mocking/argument-matchers.md#anyargs--match-every-parameter-with-one-token). + +### Capturing Arguments + +FakeItEasy captures arguments with `Captured` or in `Invokes`. In TUnit.Mocks, every matcher records the values it matches. Store the matcher in a variable and read it after the code under test runs. + +```csharp +// FakeItEasy +var repository = A.Fake(); +var savedUser = A.Captured(); +A.CallTo(() => repository.Save(savedUser._)).DoesNothing(); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +var userArg = Any(); +repository.Save(userArg); + +repository.Object.Save(new User(1, "Alice")); + +var saved = userArg.Values; // every matched value +var last = userArg.Latest; // the most recent value +``` + +See [Argument Matchers](../../writing-tests/mocking/argument-matchers.md) for range, regex, and custom matchers. + +## Out and Ref Parameters + +FakeItEasy assigns `out` and `ref` values by position with `AssignsOutAndRefParameters`. TUnit.Mocks leaves `out` parameters out of the setup signature and generates a typed `SetsOut{ParameterName}` method for each one. + +```csharp +// FakeItEasy +var repository = A.Fake(); +string ignored; +A.CallTo(() => repository.TryGetName(1, out ignored)) + .Returns(true) + .AssignsOutAndRefParameters("Alice"); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.TryGetName(1) + .Returns(true) + .SetsOutName("Alice"); +``` + +`ref` parameters stay in the setup signature and use `SetsRef{ParameterName}`. + +## Properties + +FakeItEasy fakes behave like auto-properties: a value you set is returned by the getter. TUnit.Mocks properties return defaults until you configure them. Call `SetupAllProperties()` to get FakeItEasy's behavior. + +```csharp +// FakeItEasy +var repository = A.Fake(); +A.CallTo(() => repository.ConnectionName).Returns("primary"); +A.CallToSet(() => repository.ConnectionName).To("replica").Throws(); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.ConnectionName.Returns("primary"); +repository.ConnectionName.Set("replica").Throws(); + +// Opt in to auto-property behavior +var tracked = IUserRepository.Mock(); +tracked.SetupAllProperties(); +tracked.Object.ConnectionName = "replica"; // the getter now returns "replica" +``` + +Setups made with `Returns` take precedence over values stored by `SetupAllProperties()`. + +## Verifying Calls + +Remove `A.CallTo(() => ...)`, call the member on the mock, and replace `MustHaveHappened` with `WasCalled`. + +```csharp +// FakeItEasy +var repository = A.Fake(); + +A.CallTo(() => repository.Save(A.That.Matches(u => u.Name == "Alice"))).MustHaveHappened(); +A.CallTo(() => repository.GetById(1)).MustHaveHappenedTwiceExactly(); +A.CallTo(() => repository.Save(A._)).MustNotHaveHappened(); +A.CallToSet(() => repository.ConnectionName).To("replica").MustHaveHappenedOnceExactly(); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); + +repository.Save(user => user.Name == "Alice").WasCalled(); +repository.GetById(1).WasCalled(Times.Exactly(2)); +repository.Save(Any()).WasNeverCalled(); +repository.ConnectionName.Set("replica").WasCalled(Times.Once); +``` + +| FakeItEasy | TUnit.Mocks | +|---|---| +| `MustHaveHappened()` | `WasCalled()` | +| `MustHaveHappenedOnceExactly()` | `WasCalled(Times.Once)` | +| `MustHaveHappenedTwiceExactly()` | `WasCalled(Times.Exactly(2))` | +| `MustHaveHappenedANumberOfTimesMatching(n => n == 3)` | `WasCalled(Times.Exactly(3))` | +| `MustHaveHappened(3, Times.OrMore)` | `WasCalled(Times.AtLeast(3))` | +| `MustHaveHappened(3, Times.OrLess)` | `WasCalled(Times.AtMost(3))` | +| `MustNotHaveHappened()` | `WasNeverCalled()` | + +`FakeItEasy.Times` and `TUnit.Mocks.Times` are different types. In a migrated file, `Times` refers to the TUnit.Mocks type. + +In a TUnit test, you can also await the check as an assertion. This needs the separate `TUnit.Mocks.Assertions` package (`dotnet add package TUnit.Mocks.Assertions`) and `using TUnit.Mocks.Assertions;`: + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +await Assert.That(repository.GetById(1)).WasCalled(Times.Once); +``` + +### Call Order + +FakeItEasy chains ordered checks with `.Then(...)`. TUnit.Mocks groups them in `Mock.VerifyInOrder`, which works across several mocks. + +```csharp +// FakeItEasy +var repository = A.Fake(); + +A.CallTo(() => repository.GetById(1)).MustHaveHappened() + .Then(A.CallTo(() => repository.Save(A._)).MustHaveHappened()); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); + +Mock.VerifyInOrder(() => +{ + repository.GetById(1).WasCalled(); + repository.Save(Any()).WasCalled(); +}); +``` + +### Checks FakeItEasy Does Not Have + +TUnit.Mocks also provides `mock.VerifyAll()`, which fails if a setup was never used, and `mock.VerifyNoOtherCalls()`, which fails if a call was not verified. See [Verification](../../writing-tests/mocking/verification.md). + +## Events + +```csharp +// FakeItEasy +var repository = A.Fake(); +repository.UserSaved += Raise.With(new UserSavedEventArgs(new User(1, "Alice"))); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.RaiseUserSaved(new UserSavedEventArgs(new User(1, "Alice"))); +``` + +TUnit.Mocks generates a `Raise{EventName}` method for each event. To raise an event when a method is called, chain `.Raises{EventName}(...)` on a setup. See [Events](../../writing-tests/mocking/advanced.md#events). + +## Classes + +A FakeItEasy fake of a class does not call the base implementation unless you use `CallsBaseMethod()` or `options.CallsBaseMethods()`. A TUnit.Mocks class mock always calls the base implementation for unconfigured virtual members. + +```csharp +// FakeItEasy +var calculator = A.Fake(options => options.CallsBaseMethods()); +A.CallTo(() => calculator.Discount(A._)).Returns(5m); +``` + +```csharp +// TUnit.Mocks +var calculator = PriceCalculator.Mock(); +calculator.Discount(Any()).Returns(5m); + +decimal tax = calculator.Object.Tax(100m); // base implementation: 20 +``` + +If a test depends on FakeItEasy's default behavior, configure each virtual member that the test calls. TUnit.Mocks can also configure `protected` virtual and abstract members with the same syntax as public members. + +## Other Features + +| FakeItEasy | TUnit.Mocks | +|---|---| +| Members that return interfaces return fakes | Loose mocks return auto-mocks; use `Mock.Get(instance)` to configure one | +| `A.Dummy()` | No equivalent; use `T.Mock()` or a real value | +| `A.Fake>()` | `Mock.OfDelegate>()` | +| `A.CallTo(fake).Where(call => ...)` | No equivalent; configure each member | +| `Fake.GetCalls(fake)` | `mock.Invocations` (a list of `CallRecord`) | +| `Fake.ClearRecordedCalls(fake)` | `mock.Reset()`; it also clears setups and state | + +See [Advanced Features](../../writing-tests/mocking/advanced.md) for state machines, diagnostics, custom default values, and `MockRepository`. + +## Running Both Libraries Side by Side + +TUnit.Mocks adds these global usings to the project: + +- `TUnit.Mocks` +- `TUnit.Mocks.Arguments` +- `static TUnit.Mocks.Arguments.Arg` +- `TUnit.Mocks.Generated` + +Most FakeItEasy names, such as `A`, `Fake`, and `Raise`, do not conflict with them. `Times` does: in a file that has `using FakeItEasy;`, `Times.OrMore` causes error `CS0104`. If you migrate one file at a time, use one of these fixes: + +- In files that still use FakeItEasy, add `using Times = FakeItEasy.Times;`. +- Or set `disable` in the project and add the four usings above to each migrated file. + +When no file uses FakeItEasy, remove the `FakeItEasy` package reference. Also remove `FakeItEasy.Analyzer.CSharp` if the project uses it. + +## Native AOT + +FakeItEasy creates proxies at runtime with Castle DynamicProxy and `Reflection.Emit`, which Native AOT does not support. TUnit.Mocks generates mocks at compile time, so the migrated tests can run in a Native AOT or trimmed test application. See [AOT compatibility](../../writing-tests/aot.md). + +## Next Steps + +- [Setup and stubbing](../../writing-tests/mocking/setup.md) +- [Verification](../../writing-tests/mocking/verification.md) +- [Advanced features](../../writing-tests/mocking/advanced.md) diff --git a/docs/docs/migration/mocking/moq.md b/docs/docs/migration/mocking/moq.md new file mode 100644 index 00000000000..803ecdd6562 --- /dev/null +++ b/docs/docs/migration/mocking/moq.md @@ -0,0 +1,502 @@ +--- +sidebar_label: Moq +--- + +# Migrating from Moq to TUnit.Mocks + +TUnit.Mocks generates mocks at compile time instead of creating runtime proxies. Moq configures a mock with an expression, such as `mock.Setup(x => x.GetById(1))`. TUnit.Mocks generates a strongly typed member on the mock for each member of the mocked type, so you call `mock.GetById(1)` directly. The chained method decides the meaning: `.Returns()` configures a setup, and `.WasCalled()` verifies calls. + +TUnit.Mocks works with any test framework. You can migrate mocks before, after, or without migrating the tests to TUnit. + +## Before You Start + +1. Add the package: + + ```bash + dotnet add package TUnit.Mocks + ``` + +2. Set `14` (or `preview`) in the test project. TUnit.Mocks requires C# 14. Older versions report error `TM004`. +3. Read [Running Both Libraries Side by Side](#running-both-libraries-side-by-side) if you migrate one file at a time. `Mock`, `Times`, `MockBehavior`, and `MockRepository` exist in both libraries. + +## Quick Reference + +| Moq | TUnit.Mocks | +|---|---| +| `new Mock()` | `IService.Mock()` | +| `new Mock(MockBehavior.Strict)` | `IService.Mock(MockBehavior.Strict)` | +| `new Mock(arg1, arg2) { CallBase = true }` | `MyClass.Mock(arg1, arg2)` | +| `mock.Object` | `mock.Object`, or `mock` itself (converts implicitly) | +| `mock.Setup(x => x.Method(1)).Returns(value)` | `mock.Method(1).Returns(value)` | +| `mock.Setup(x => x.MethodAsync(1)).ReturnsAsync(value)` | `mock.MethodAsync(1).Returns(value)` | +| `.Returns((int id) => ...)` | `.Returns((int id) => ...)` | +| `.Throws()` / `.ThrowsAsync(ex)` | `.Throws()` / `.Throws(ex)` | +| `.Callback(id => ...)` | `.Callback((int id) => ...)` | +| `mock.SetupSequence(...)` | `.ReturnsSequentially(...)` or `.Then()` | +| `mock.SetupGet(x => x.Prop).Returns(value)` | `mock.Prop.Returns(value)` | +| `mock.SetupProperty(x => x.Prop)` / `mock.SetupAllProperties()` | `mock.SetupAllProperties()` | +| `It.IsAny()` | `Any()` or `Any()` | +| `It.Is(x => ...)` | `x => ...` or `Is(x => ...)` | +| `It.IsIn(...)` / `It.IsNotIn(...)` | `IsIn(...)` / `IsNotIn(...)` | +| `It.IsRegex(pattern)` | `Matches(pattern)` | +| `It.IsNotNull()` | `IsNotNull()` | +| `mock.Verify(x => x.Method(1), Times.Once())` | `mock.Method(1).WasCalled(Times.Once)` | +| `mock.Verify(x => x.Method(1), Times.Never())` | `mock.Method(1).WasNeverCalled()` | +| `mock.VerifySet(x => x.Prop = value)` | `mock.Prop.Set(value).WasCalled()` | +| `mock.Verify()` (`.Verifiable()` setups) | `.WasCalled()` on each of those calls | +| `mock.VerifyAll()` | `mock.VerifyAll()` (does not mark calls as verified for `VerifyNoOtherCalls()`) | +| `mock.VerifyNoOtherCalls()` | `mock.VerifyNoOtherCalls()` | +| `mock.Raise(x => x.Event += null, args)` | `mock.Raise{EventName}(args)` | +| `mock.As()` | `Mock.Of()` | +| `Mock.Get(instance)` | `Mock.Get(instance)` | +| `mock.Invocations` | `mock.Invocations` | +| `mock.Reset()` | `mock.Reset()` | +| `new MockRepository(MockBehavior.Strict)` | `new MockRepository(MockBehavior.Strict)` | + + + +## Example Types + +The examples on this page use these types: + +```csharp +public record User(int Id, string Name); + +public sealed class UserSavedEventArgs(User user) : EventArgs +{ + public User User { get; } = user; +} + +public interface IUserRepository +{ + User? GetById(int id); + Task GetByIdAsync(int id); + void Save(User user); + bool TryGetName(int id, out string name); + string ConnectionName { get; set; } + event EventHandler? UserSaved; +} + +public abstract class PriceCalculator +{ + public virtual decimal Tax(decimal amount) => amount * 0.2m; + public abstract decimal Discount(decimal amount); +} +``` + +## Creating Mocks + +Both libraries return a `Mock` wrapper. For interfaces, the TUnit.Mocks wrapper also implements the interface, so you can pass the mock directly to the code under test. `.Object` still works. + +```csharp +// Moq +var repository = new Mock(); +IUserRepository sut = repository.Object; +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +IUserRepository sut = repository; // implicit conversion +IUserRepository same = repository.Object; // explicit instance +``` + +`T.Mock()` requires C# 14. You can also use the factory form `Mock.Of()`. Moq's `Mock.Of()` returns the mocked object, but TUnit.Mocks' `Mock.Of()` returns the `Mock` wrapper. + +Both libraries are loose by default. `MockBehavior.Strict` makes unconfigured calls throw. TUnit.Mocks throws `MockStrictBehaviorException`. + +```csharp +// Moq +var repository = new Mock(MockBehavior.Strict); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(MockBehavior.Strict); +``` + +## Return Values + +Remove the `Setup(x => ...)` expression and call the member on the mock. + +```csharp +// Moq +var repository = new Mock(); +repository.Setup(x => x.GetById(1)).Returns(new User(1, "Alice")); +repository.Setup(x => x.GetById(It.IsAny())).Returns(new User(0, "Anyone")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1).Returns(new User(1, "Alice")); +repository.GetById(Any()).Returns(new User(0, "Anyone")); +``` + +In both libraries, the most recently added matching setup wins. + +### Computed Return Values + +The lambda syntax is the same. The parameters are the method's arguments. + +```csharp +// Moq +var repository = new Mock(); +repository.Setup(x => x.GetById(It.IsAny())).Returns((int id) => new User(id, "Generated")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(Any()).Returns((int id) => new User(id, "Generated")); +``` + +### Async Methods + +Moq uses `ReturnsAsync`. In TUnit.Mocks, `Returns` wraps the value in `Task` or `ValueTask` for you. + +```csharp +// Moq +var repository = new Mock(); +repository.Setup(x => x.GetByIdAsync(1)).ReturnsAsync(new User(1, "Alice")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetByIdAsync(1).Returns(new User(1, "Alice")); +``` + +TUnit.Mocks also has `ReturnsAsync`, but it takes a task, for example one from a `TaskCompletionSource`. Use it when the test controls when the task completes. + +### Sequences + +`SetupSequence` maps to `ReturnsSequentially` for values and to `.Then()` for mixed behavior. + +```csharp +// Moq +var repository = new Mock(); +repository.SetupSequence(x => x.GetById(1)) + .Throws() + .Returns(new User(1, "First")) + .Returns(new User(1, "Second")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1) + .Throws() + .Then() + .ReturnsSequentially(new User(1, "First"), new User(1, "Second")); +``` + +When a Moq sequence ends, later calls return the default value. In TUnit.Mocks, the last behavior repeats. In this example, every call after the third call returns `"Second"`. + +Without `.Then()`, chained behaviors apply to the same call. For example, `.Returns(a).Returns(b)` always returns `b`. + +## Exceptions + +```csharp +// Moq +var repository = new Mock(); +repository.Setup(x => x.GetById(-1)).Throws(new ArgumentOutOfRangeException("id")); +repository.Setup(x => x.GetByIdAsync(-1)).ThrowsAsync(new InvalidOperationException()); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(-1).Throws(new ArgumentOutOfRangeException("id")); +repository.GetByIdAsync(-1).Throws(); +``` + +TUnit.Mocks has no `ThrowsAsync`. For a method that returns `Task` or `ValueTask`, `Throws` returns a faulted task. It does not throw synchronously. + +## Callbacks and Void Methods + +Moq passes arguments to `Callback(...)` through generic type arguments. TUnit.Mocks generates typed overloads, so declare the lambda parameter types. + +```csharp +// Moq +var repository = new Mock(); +var saved = new List(); +repository.Setup(x => x.Save(It.IsAny())).Callback(user => saved.Add(user)); +repository.Setup(x => x.Save(It.Is(u => u.Id < 0))).Throws(new ArgumentException("Invalid id")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +var saved = new List(); +repository.Save(Any()).Callback((User user) => saved.Add(user)); +repository.Save(user => user.Id < 0).Throws(new ArgumentException("Invalid id")); +``` + +In strict mode, a void setup with no chained behavior allows the call: `repository.Save(Any());`. It replaces `Setup(x => x.Save(It.IsAny()))` with no `Callback`. + +## Argument Matchers + +TUnit.Mocks imports its matchers globally, so you do not need a prefix like `It.`. A raw value is an exact match, and a lambda is a predicate. + +| Moq | TUnit.Mocks | +|---|---| +| `It.IsAny()` | `Any()` or `Any()` | +| `5` | `5` or `Is(5)` | +| `It.Is(x => x > 0)` | `x => x > 0` or `Is(x => x > 0)` | +| `It.IsIn(1, 2, 3)` | `IsIn(1, 2, 3)` | +| `It.IsNotIn(1, 2, 3)` | `IsNotIn(1, 2, 3)` | +| `It.IsInRange(1, 10, Range.Inclusive)` | `IsInRange(1, 10)` (always inclusive) | +| `It.IsRegex(pattern)` | `Matches(pattern)` | +| `It.IsNotNull()` | `IsNotNull()` | +| `It.Is(x => x == null)` | `IsNull()` | +| `It.Ref.IsAny` | `Any()` | +| `Capture.In(list)` | Store an `Any()` matcher and read `.Values` | + +```csharp +// Moq +var repository = new Mock(); +repository.Setup(x => x.GetById(It.Is(id => id > 100))).Returns(new User(101, "Admin")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(id => id > 100).Returns(new User(101, "Admin")); +``` + +### Capturing Arguments + +Every TUnit.Mocks matcher records the values it matches. Store the matcher in a variable and read it after the code under test runs. + +```csharp +// Moq +var repository = new Mock(); +var saved = new List(); +repository.Setup(x => x.Save(Capture.In(saved))); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +var userArg = Any(); +repository.Save(userArg); + +repository.Object.Save(new User(1, "Alice")); + +var saved = userArg.Values; // every matched value +var last = userArg.Latest; // the most recent value +``` + +See [Argument Matchers](../../writing-tests/mocking/argument-matchers.md) for collection, range, and custom matchers. + +## Out and Ref Parameters + +Moq takes the `out` value from a variable in the setup expression. TUnit.Mocks leaves `out` parameters out of the setup signature and generates a `SetsOut{ParameterName}` method for each one. + +```csharp +// Moq +var repository = new Mock(); +var name = "Alice"; +repository.Setup(x => x.TryGetName(1, out name)).Returns(true); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.TryGetName(1) + .Returns(true) + .SetsOutName("Alice"); +``` + +`ref` parameters stay in the setup signature and use `SetsRef{ParameterName}`. + +## Properties + +TUnit.Mocks exposes each property on the mock. The getter is the default target, and `.Setter` or `.Set(value)` targets the setter. + +```csharp +// Moq +var repository = new Mock(); +repository.SetupGet(x => x.ConnectionName).Returns("primary"); +repository.SetupSet(x => x.ConnectionName = "replica").Throws(); +repository.SetupAllProperties(); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.ConnectionName.Returns("primary"); +repository.ConnectionName.Set("replica").Throws(); +repository.SetupAllProperties(); +``` + +`SetupAllProperties()` makes every property store and return values. TUnit.Mocks has no per-property `SetupProperty`; use `SetupAllProperties()`, or configure the getter with `Returns`. Setups made with `Returns` take precedence over stored values. + +## Verifying Calls + +Remove the `Verify(x => ...)` expression, call the member on the mock, and chain `WasCalled` or `WasNeverCalled`. In TUnit.Mocks, `Times` members such as `Times.Once` are properties, not methods. + +```csharp +// Moq +var repository = new Mock(); + +repository.Verify(x => x.Save(It.Is(u => u.Name == "Alice"))); +repository.Verify(x => x.GetById(1), Times.Exactly(2)); +repository.Verify(x => x.Save(It.IsAny()), Times.Never()); +repository.VerifySet(x => x.ConnectionName = "replica", Times.Once()); +repository.VerifyGet(x => x.ConnectionName, Times.AtLeastOnce()); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); + +repository.Save(user => user.Name == "Alice").WasCalled(); +repository.GetById(1).WasCalled(Times.Exactly(2)); +repository.Save(Any()).WasNeverCalled(); +repository.ConnectionName.Set("replica").WasCalled(Times.Once); +repository.ConnectionName.WasCalled(Times.AtLeastOnce); +``` + +| Moq | TUnit.Mocks | +|---|---| +| `Times.Once()` | `Times.Once` | +| `Times.Never()` | `Times.Never` or `.WasNeverCalled()` | +| `Times.AtLeastOnce()` | `Times.AtLeastOnce` or `.WasCalled()` | +| `Times.Exactly(n)` | `Times.Exactly(n)` | +| `Times.AtLeast(n)` / `Times.AtMost(n)` | `Times.AtLeast(n)` / `Times.AtMost(n)` | +| `Times.Between(min, max, Range.Inclusive)` | `Times.Between(min, max)` (always inclusive) | + +Both libraries accept a failure message: `.WasCalled(Times.Once, "Save should run once")`. + +In a TUnit test, you can also await the check as an assertion. This needs the separate `TUnit.Mocks.Assertions` package (`dotnet add package TUnit.Mocks.Assertions`) and `using TUnit.Mocks.Assertions;`: + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +await Assert.That(repository.GetById(1)).WasCalled(Times.Once); +``` + +### Verifiable Setups and VerifyAll + +Moq's `mock.Verify()` checks only setups marked `.Verifiable()`. TUnit.Mocks has no `Verifiable()`, so verify each of those calls with `WasCalled()`. This also marks the call as verified for `VerifyNoOtherCalls()`. Do not replace `Verify()` with `VerifyAll()`: `VerifyAll()` also fails for unused setups that were not verifiable, and it does not mark calls as verified. + +```csharp +// Moq +var repository = new Mock(); +repository.Setup(x => x.GetById(1)).Returns(new User(1, "Alice")).Verifiable(); + +repository.Object.GetById(1); // the code under test + +repository.Verify(); +repository.VerifyNoOtherCalls(); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1).Returns(new User(1, "Alice")); + +repository.Object.GetById(1); // the code under test + +repository.GetById(1).WasCalled(); +repository.VerifyNoOtherCalls(); +``` + +### Call Order + +Moq checks order with `MockSequence` and `InSequence`, which configure strict setups. TUnit.Mocks checks order after the code runs, across one or more mocks: + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); + +Mock.VerifyInOrder(() => +{ + repository.GetById(1).WasCalled(); + repository.Save(Any()).WasCalled(); +}); +``` + +## Events + +```csharp +// Moq +var repository = new Mock(); +repository.Raise(x => x.UserSaved += null, new UserSavedEventArgs(new User(1, "Alice"))); +repository.Setup(x => x.Save(It.IsAny())) + .Raises(x => x.UserSaved += null, new UserSavedEventArgs(new User(2, "Bob"))); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.RaiseUserSaved(new UserSavedEventArgs(new User(1, "Alice"))); +repository.Save(Any()) + .RaisesUserSaved(new UserSavedEventArgs(new User(2, "Bob"))); +``` + +TUnit.Mocks generates a `Raise{EventName}` method and a `.Raises{EventName}` setup method for each event. See [Events](../../writing-tests/mocking/advanced.md#events). + +## Classes and Protected Members + +Moq calls the base implementation only when `CallBase = true`. A TUnit.Mocks class mock always calls the base implementation for unconfigured virtual members. Pass constructor arguments as typed parameters. + +```csharp +// Moq +var calculator = new Mock { CallBase = true }; +calculator.Setup(x => x.Discount(It.IsAny())).Returns(5m); +``` + +```csharp +// TUnit.Mocks +var calculator = PriceCalculator.Mock(); +calculator.Discount(Any()).Returns(5m); + +decimal tax = calculator.Object.Tax(100m); // base implementation: 20 +``` + +If a test depends on Moq's default `CallBase = false`, configure each virtual member that the test calls. + +Moq configures `protected` members with `mock.Protected().Setup("Name", ...)` and string names. TUnit.Mocks generates typed members for `protected` virtual and abstract members, so you configure and verify them like public members. + +## Other Features + +| Moq | TUnit.Mocks | +|---|---| +| `DefaultValue.Mock` | Default in loose mode: members that return interfaces return auto-mocks | +| `mock.DefaultValueProvider = ...` | `mock.DefaultValueProvider = ...` (implement `IDefaultValueProvider`) | +| `mock.As()` | `Mock.Of()` (up to four types) | +| `Mock.Of(x => x.Prop == value)` | `IService.Mock()` and then `mock.Prop.Returns(value)` | +| `new Mock>()` | `Mock.OfDelegate>()` | +| `mock.Invocations.Clear()` | `mock.Reset()` (also clears setups and state) | +| `Mock.Get(instance)` | `Mock.Get(instance)` | + +To wrap an existing instance of a non-sealed class and override only some of its virtual members, use `Mock.Wrap(instance)`. `Mock.Wrap` does not support interfaces. See [Advanced Features](../../writing-tests/mocking/advanced.md) for state machines, diagnostics, and `MockRepository`. + +## Running Both Libraries Side by Side + +TUnit.Mocks adds these global usings to the project: + +- `TUnit.Mocks` +- `TUnit.Mocks.Arguments` +- `static TUnit.Mocks.Arguments.Arg` +- `TUnit.Mocks.Generated` + +In a file that also has `using Moq;`, the names `Mock`, `Mock`, `MockBehavior`, `MockRepository`, and `Times` each refer to two types. That causes error `CS0104`. If you migrate one file at a time, use one of these fixes: + +- Set `disable` in the project and add the four usings above to each migrated file. Files that still use Moq then compile unchanged. +- Or, in files that still use Moq, move `using Moq;` inside the file's namespace declaration. A using directive inside a namespace takes precedence over global usings. + +When no file uses Moq, remove the `Moq` package reference. + +## Native AOT + +Moq creates proxies at runtime with Castle DynamicProxy and `Reflection.Emit`, which Native AOT does not support. TUnit.Mocks generates mocks at compile time, so the migrated tests can run in a Native AOT or trimmed test application. See [AOT compatibility](../../writing-tests/aot.md). + +## Next Steps + +- [Setup and stubbing](../../writing-tests/mocking/setup.md) +- [Verification](../../writing-tests/mocking/verification.md) +- [Advanced features](../../writing-tests/mocking/advanced.md) diff --git a/docs/docs/migration/mocking/nsubstitute.md b/docs/docs/migration/mocking/nsubstitute.md new file mode 100644 index 00000000000..44884f424d3 --- /dev/null +++ b/docs/docs/migration/mocking/nsubstitute.md @@ -0,0 +1,484 @@ +--- +sidebar_label: NSubstitute +--- + +# Migrating from NSubstitute to TUnit.Mocks + +TUnit.Mocks generates mocks at compile time instead of creating runtime proxies. The API is close to NSubstitute's: you call the member you want to configure and chain `.Returns()`. The main change is where the call happens. NSubstitute configures calls on the substitute itself. TUnit.Mocks configures them on a `Mock` wrapper, which is also usable as the mocked interface. + +TUnit.Mocks works with any test framework. You can migrate mocks before, after, or without migrating the tests to TUnit. + +## Before You Start + +1. Add the package: + + ```bash + dotnet add package TUnit.Mocks + ``` + +2. Set `14` (or `preview`) in the test project. TUnit.Mocks requires C# 14. Older versions report error `TM004`. +3. Read [Running Both Libraries Side by Side](#running-both-libraries-side-by-side) if you migrate one file at a time. TUnit.Mocks adds global usings, and its `Arg` type conflicts with `NSubstitute.Arg`. + +## Quick Reference + +| NSubstitute | TUnit.Mocks | +|---|---| +| `Substitute.For()` | `IService.Mock()` | +| `Substitute.For()` | `Mock.Of()` | +| `Substitute.ForPartsOf()` | `MyClass.Mock()` (unconfigured virtual members call the base implementation) | +| `Substitute.For>()` | `Mock.OfDelegate>()` | +| Substitute passed to the code under test | `mock` (converts implicitly) or `mock.Object` | +| `sub.Method(1).Returns(value)` | `mock.Method(1).Returns(value)` | +| `sub.Method(1).Returns(a, b, c)` | `mock.Method(1).ReturnsSequentially(a, b, c)` | +| `sub.Method(1).Returns(call => ...)` | `mock.Method(1).Returns((int id) => ...)` | +| `sub.MethodAsync(1).Returns(value)` | `mock.MethodAsync(1).Returns(value)` | +| `sub.Method(1).ReturnsForAnyArgs(value)` | `mock.Method(Any()).Returns(value)` (`Any()` for each parameter; see [Ignoring Arguments](#ignoring-arguments)) | +| `sub.Method(1).Throws(ex)` / `.ThrowsAsync(ex)` | `mock.Method(1).Throws(ex)` | +| `sub.When(x => x.Void()).Do(call => ...)` | `mock.Void().Callback(() => ...)` | +| `Arg.Any()` | `Any()` or `Any()` | +| `Arg.Is(value)` | `value` or `Is(value)` | +| `Arg.Is(x => ...)` | `x => ...` or `Is(x => ...)` | +| `Arg.Do(x => list.Add(x))` | `var arg = Any();` then read `arg.Values` | +| `sub.Received().Method(1)` | `mock.Method(1).WasCalled()` | +| `sub.Received(3).Method(1)` | `mock.Method(1).WasCalled(Times.Exactly(3))` | +| `sub.DidNotReceive().Method(1)` | `mock.Method(1).WasNeverCalled()` | +| `sub.ReceivedWithAnyArgs().Method(default)` | `mock.Method(Any()).WasCalled()` (`Any()` for each parameter; see [Ignoring Arguments](#ignoring-arguments)) | +| `Received.InOrder(() => { ... })` | `Mock.VerifyInOrder(() => { ... })` | +| `sub.ReceivedCalls()` | `mock.Invocations` | +| `sub.ClearReceivedCalls()` | `mock.Reset()` (also clears setups) | +| `sub.Event += Raise.EventWith(args)` | `mock.Raise{EventName}(args)` | +| Auto-properties on substitutes | `mock.SetupAllProperties()` | + + + +## Example Types + +The examples on this page use these types: + +```csharp +public record User(int Id, string Name); + +public sealed class UserSavedEventArgs(User user) : EventArgs +{ + public User User { get; } = user; +} + +public interface IUserRepository +{ + User? GetById(int id); + Task GetByIdAsync(int id); + void Save(User user); + bool TryGetName(int id, out string name); + string ConnectionName { get; set; } + event EventHandler? UserSaved; +} + +public abstract class PriceCalculator +{ + public virtual decimal Tax(decimal amount) => amount * 0.2m; + public abstract decimal Discount(decimal amount); +} +``` + +## Creating Mocks + +NSubstitute returns the substitute as the interface type. TUnit.Mocks returns a `Mock` wrapper. For interfaces, the wrapper also implements the interface, so you can pass it directly to the code under test. Use `.Object` when you need the `T` instance explicitly. + +```csharp +// NSubstitute +var repository = Substitute.For(); +IUserRepository sut = repository; +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +IUserRepository sut = repository; // implicit conversion +IUserRepository same = repository.Object; // explicit instance +``` + +`T.Mock()` requires C# 14. You can also use the factory form `Mock.Of()`. + +NSubstitute has no strict mode. TUnit.Mocks is loose by default, like NSubstitute. Pass `MockBehavior.Strict` to make unconfigured calls throw `MockStrictBehaviorException`: + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(MockBehavior.Strict); +``` + +## Return Values + +The basic setup is almost identical. The call goes to the mock wrapper instead of the substitute. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetById(1).Returns(new User(1, "Alice")); +repository.GetById(Arg.Any()).Returns(new User(0, "Anyone")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1).Returns(new User(1, "Alice")); +repository.GetById(Any()).Returns(new User(0, "Anyone")); +``` + +In both libraries, the most recently added matching setup wins. + +### Computed Return Values + +NSubstitute passes a `CallInfo` and you read arguments by type or position. TUnit.Mocks passes the method's arguments as typed lambda parameters. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetById(Arg.Any()).Returns(call => new User(call.Arg(), "Generated")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(Any()).Returns((int id) => new User(id, "Generated")); +``` + +### Sequential Return Values + +NSubstitute's multi-value `Returns(a, b, c)` maps to `ReturnsSequentially`. In both libraries, the last value repeats after the sequence ends. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetById(1).Returns(new User(1, "First"), new User(1, "Second")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1).ReturnsSequentially(new User(1, "First"), new User(1, "Second")); +``` + +To mix return values and exceptions across calls, chain `.Then()`: + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(1) + .Throws() + .Then() + .Returns(new User(1, "Alice")); +``` + +### Async Methods + +Both libraries accept the unwrapped value for `Task` and `ValueTask` members. TUnit.Mocks wraps it in the task for you, so async setups usually need no change. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetByIdAsync(1).Returns(new User(1, "Alice")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetByIdAsync(1).Returns(new User(1, "Alice")); +``` + +To return a task you create yourself, such as one from a `TaskCompletionSource`, use `ReturnsAsync(task)`. + +### Ignoring Arguments + +`ReturnsForAnyArgs` and `ReceivedWithAnyArgs` have no direct equivalent. Pass `Any()` for each parameter. Some methods also get an `AnyArgs()` shortcut that replaces all of them. It is not generated for overloaded or generic methods, for methods with fewer than two matchable parameters, or for methods with `out`, `ref`, or ref-struct parameters. See [when the shortcut is generated](../../writing-tests/mocking/argument-matchers.md#anyargs--match-every-parameter-with-one-token). + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetById(default).ReturnsForAnyArgs(new User(0, "Anyone")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(Any()).Returns(new User(0, "Anyone")); +``` + +## Exceptions + +NSubstitute uses `Throws` and `ThrowsAsync` from `NSubstitute.ExceptionExtensions`. TUnit.Mocks uses `Throws` for both. For a method that returns `Task` or `ValueTask`, the mock returns a faulted task. It does not throw synchronously. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetById(-1).Throws(new ArgumentOutOfRangeException("id")); +repository.GetByIdAsync(-1).ThrowsAsync(new InvalidOperationException()); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(-1).Throws(new ArgumentOutOfRangeException("id")); +repository.GetByIdAsync(-1).Throws(); +``` + +## Callbacks and Void Methods + +NSubstitute configures void members with `When(...).Do(...)`. In TUnit.Mocks, call the void member on the mock and chain `Callback` or `Throws`. Typed callbacks receive the method's arguments. + +```csharp +// NSubstitute +var repository = Substitute.For(); +var saved = new List(); +repository.When(x => x.Save(Arg.Any())).Do(call => saved.Add(call.Arg())); +repository.When(x => x.Save(Arg.Is(u => u.Id < 0))).Do(_ => throw new ArgumentException("Invalid id")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +var saved = new List(); +repository.Save(Any()).Callback((User user) => saved.Add(user)); +repository.Save(user => user.Id < 0).Throws(new ArgumentException("Invalid id")); +``` + +`Callback` and `Throws` work on methods with return values too. For example, `.Callback(...).Returns(...)` runs the callback and returns the value. + +## Argument Matchers + +TUnit.Mocks imports its matchers globally, so you do not need the `Arg.` prefix. A raw value is an exact match, and a lambda is a predicate. + +| NSubstitute | TUnit.Mocks | +|---|---| +| `Arg.Any()` | `Any()` or `Any()` | +| `Arg.Is(5)` | `5` or `Is(5)` | +| `Arg.Is(x => x > 0)` | `x => x > 0` or `Is(x => x > 0)` | +| `Arg.Is(x => x == null)` | `IsNull()` | +| `Arg.Is(x => x != null)` | `IsNotNull()` | +| `Arg.Is(x => Regex.IsMatch(x, pattern))` | `Matches(pattern)` | +| `Arg.Do(x => list.Add(x))` | Store an `Any()` matcher and read `.Values` or `.Latest` | + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.GetById(Arg.Is(id => id > 100)).Returns(new User(101, "Admin")); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.GetById(id => id > 100).Returns(new User(101, "Admin")); +``` + +### Capturing Arguments + +NSubstitute captures arguments with `Arg.Do`. In TUnit.Mocks, every matcher records the values it matches. Store the matcher in a variable and read it after the code under test runs. + +```csharp +// NSubstitute +var repository = Substitute.For(); +var saved = new List(); +repository.Save(Arg.Do(user => saved.Add(user))); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +var userArg = Any(); +repository.Save(userArg); + +repository.Object.Save(new User(1, "Alice")); + +var saved = userArg.Values; // every matched value +var last = userArg.Latest; // the most recent value +``` + +See [Argument Matchers](../../writing-tests/mocking/argument-matchers.md) for collection, range, and custom matchers. + +## Out and Ref Parameters + +NSubstitute sets `out` values through the `CallInfo` argument array. TUnit.Mocks leaves `out` parameters out of the setup signature and generates a `SetsOut{ParameterName}` method for each one. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.TryGetName(1, out _).Returns(call => +{ + call[1] = "Alice"; + return true; +}); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.TryGetName(1) + .Returns(true) + .SetsOutName("Alice"); +``` + +`ref` parameters stay in the setup signature and use `SetsRef{ParameterName}`. + +## Properties + +NSubstitute substitutes behave like auto-properties: a value you set is returned by the getter. TUnit.Mocks properties return defaults until you configure them. Call `SetupAllProperties()` to get NSubstitute's behavior. + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.ConnectionName.Returns("primary"); + +repository.ConnectionName = "replica"; // the getter now returns "replica" +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.ConnectionName.Returns("primary"); + +// Opt in to auto-property behavior +var tracked = IUserRepository.Mock(); +tracked.SetupAllProperties(); +tracked.Object.ConnectionName = "replica"; // the getter now returns "replica" +``` + +Setups made with `Returns` take precedence over values stored by `SetupAllProperties()`. + +## Verifying Calls + +NSubstitute checks calls with `Received()` before the member. TUnit.Mocks calls the member on the mock and then chains `WasCalled()` or `WasNeverCalled()`. Both throw when the check fails. + +```csharp +// NSubstitute +var repository = Substitute.For(); + +repository.Received().Save(Arg.Is(u => u.Name == "Alice")); +repository.Received(2).GetById(1); +repository.DidNotReceive().Save(Arg.Is(u => u.Id < 0)); +repository.ReceivedWithAnyArgs().GetById(default); +repository.Received().ConnectionName = "replica"; +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); + +repository.Save(user => user.Name == "Alice").WasCalled(); +repository.GetById(1).WasCalled(Times.Exactly(2)); +repository.Save(user => user.Id < 0).WasNeverCalled(); +repository.GetById(Any()).WasCalled(); +repository.ConnectionName.Set("replica").WasCalled(); +``` + +`WasCalled()` without arguments means at least once, like `Received()`. The `Times` class also has `Once`, `Never`, `AtLeastOnce`, `AtLeast(n)`, `AtMost(n)`, and `Between(min, max)`. + +In a TUnit test, you can also await the check as an assertion. This needs the separate `TUnit.Mocks.Assertions` package (`dotnet add package TUnit.Mocks.Assertions`) and `using TUnit.Mocks.Assertions;`: + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +await Assert.That(repository.GetById(1)).WasCalled(Times.Once); +``` + +### Call Order + +```csharp +// NSubstitute +var repository = Substitute.For(); + +Received.InOrder(() => +{ + repository.GetById(1); + repository.Save(Arg.Any()); +}); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); + +Mock.VerifyInOrder(() => +{ + repository.GetById(1).WasCalled(); + repository.Save(Any()).WasCalled(); +}); +``` + +`Mock.VerifyInOrder` works across several mocks. + +### Checks NSubstitute Does Not Have + +TUnit.Mocks also provides `mock.VerifyAll()`, which fails if a setup was never used, and `mock.VerifyNoOtherCalls()`, which fails if a call was not verified. See [Verification](../../writing-tests/mocking/verification.md). + +## Events + +```csharp +// NSubstitute +var repository = Substitute.For(); +repository.UserSaved += Raise.EventWith(new UserSavedEventArgs(new User(1, "Alice"))); +``` + +```csharp +// TUnit.Mocks +var repository = IUserRepository.Mock(); +repository.RaiseUserSaved(new UserSavedEventArgs(new User(1, "Alice"))); +``` + +TUnit.Mocks generates a `Raise{EventName}` method for each event. To raise an event when a method is called, chain `.Raises{EventName}(...)` on a setup. See [Events](../../writing-tests/mocking/advanced.md#events). + +## Partial Mocks and Classes + +`Substitute.ForPartsOf()` calls the real implementation for members you do not configure. A TUnit.Mocks class mock does the same for every unconfigured virtual member, so `T.Mock()` replaces `ForPartsOf()`. + +```csharp +// NSubstitute +var calculator = Substitute.ForPartsOf(); +calculator.Discount(Arg.Any()).Returns(5m); +``` + +```csharp +// TUnit.Mocks +var calculator = PriceCalculator.Mock(); +calculator.Discount(Any()).Returns(5m); + +decimal tax = calculator.Object.Tax(100m); // base implementation: 20 +``` + +This is different from `Substitute.For()`, which does not call the base implementation of virtual members. If you depend on that, configure each member you call. Pass constructor arguments as typed parameters: `MyService.Mock("connection", 42)`. + +TUnit.Mocks can also configure `protected` virtual and abstract members with the same syntax as public members. To wrap an existing instance of a non-sealed class and override only some of its virtual members, use `Mock.Wrap(instance)`. `Mock.Wrap` does not support interfaces. + +## Other Features + +| NSubstitute | TUnit.Mocks | +|---|---| +| Recursive mocks (interface members return substitutes) | Loose mocks return auto-mocks; use `Mock.Get(instance)` to configure one | +| `sub.ReceivedCalls()` | `mock.Invocations` (a list of `CallRecord`) | +| `sub.ClearReceivedCalls()` | `mock.Reset()`; it also clears setups and state | +| `Substitute.For()` | `Mock.Of()` (up to four types) | +| `.Configure()` before setting up a partial | Not needed; setup never calls the real member | + +## Running Both Libraries Side by Side + +TUnit.Mocks adds these global usings to the project: + +- `TUnit.Mocks` +- `TUnit.Mocks.Arguments` +- `static TUnit.Mocks.Arguments.Arg` +- `TUnit.Mocks.Generated` + +In a file that also has `using NSubstitute;`, the name `Arg` refers to two types. That causes error `CS0104`. If you migrate one file at a time, use one of these fixes: + +- In files that still use NSubstitute, add `using Arg = NSubstitute.Arg;`. +- Or set `disable` in the project and add the four usings above to each migrated file. + +When no file uses NSubstitute, remove the `NSubstitute` package reference. Also remove `NSubstitute.Analyzers` if the project uses it. + +## Native AOT + +NSubstitute creates proxies at runtime with `Reflection.Emit`, which Native AOT does not support. TUnit.Mocks generates mocks at compile time, so the migrated tests can run in a Native AOT or trimmed test application. See [AOT compatibility](../../writing-tests/aot.md). + +## Next Steps + +- [Setup and stubbing](../../writing-tests/mocking/setup.md) +- [Verification](../../writing-tests/mocking/verification.md) +- [Advanced features](../../writing-tests/mocking/advanced.md): state machines, auto-mocking, diagnostics, and `MockRepository` diff --git a/docs/docs/writing-tests/mocking/index.md b/docs/docs/writing-tests/mocking/index.md index ab9ce1cd5fe..c88f8471b1f 100644 --- a/docs/docs/writing-tests/mocking/index.md +++ b/docs/docs/writing-tests/mocking/index.md @@ -210,3 +210,4 @@ See [Argument Matchers](argument-matchers) for the full API. - [Advanced Features](advanced) — state machines, events, auto-mocking, diagnostics, and more - [HTTP Mocking](http) — mock `HttpClient` with `MockHttpHandler` - [Logging](logging) — capture and verify `ILogger` calls with `MockLogger` +- Migrating from another library — [Moq](../../migration/mocking/moq.md), [NSubstitute](../../migration/mocking/nsubstitute.md), or [FakeItEasy](../../migration/mocking/fakeiteasy.md) diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 67f2f9094bb..03307acf5cd 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -227,6 +227,16 @@ const sidebars: SidebarsConfig = { 'migration/xunit', 'migration/nunit', 'migration/mstest', + { + type: 'category', + label: 'Mocking Libraries', + collapsed: true, + items: [ + 'migration/mocking/moq', + 'migration/mocking/nsubstitute', + 'migration/mocking/fakeiteasy', + ], + }, ], }, { diff --git a/skills/tunit/SKILL.md b/skills/tunit/SKILL.md index 57fc92072b4..dd5e60e5497 100644 --- a/skills/tunit/SKILL.md +++ b/skills/tunit/SKILL.md @@ -1,6 +1,6 @@ --- name: tunit -description: Write or troubleshoot tests using TUnit, TUnit.Assertions, or TUnit.Mocks, or migrate tests to TUnit. Routes to task-specific official documentation. Use only for tasks involving these packages or migration to TUnit. +description: Write or troubleshoot tests using TUnit, TUnit.Assertions, or TUnit.Mocks, or migrate tests or mocks to TUnit or TUnit.Mocks. Routes to task-specific official documentation. Use only for tasks involving these packages or migration to them. license: MIT --- @@ -32,6 +32,7 @@ Choose the link that matches the task; these are alternatives, not a reading che | Run tests or select tests | [Running tests](https://tunit.dev/docs/getting-started/running-your-tests.md) or [filters](https://tunit.dev/docs/execution/test-filters.md) | | Migrate an existing suite | [xUnit](https://tunit.dev/docs/migration/xunit.md), [NUnit](https://tunit.dev/docs/migration/nunit.md), or [MSTest](https://tunit.dev/docs/migration/mstest.md), matching the source framework | | Mocking | [TUnit.Mocks](https://tunit.dev/docs/writing-tests/mocking.md) | +| Migrate mocks to TUnit.Mocks | [Moq](https://tunit.dev/docs/migration/mocking/moq.md), [NSubstitute](https://tunit.dev/docs/migration/mocking/nsubstitute.md), or [FakeItEasy](https://tunit.dev/docs/migration/mocking/fakeiteasy.md), matching the source library | | Aspire integration testing | [Aspire](https://tunit.dev/docs/examples/aspire.md) | | ASP.NET Core integration testing | [ASP.NET Core](https://tunit.dev/docs/examples/aspnet.md) | | Playwright browser testing | [Playwright](https://tunit.dev/docs/examples/playwright.md) | diff --git a/tests/TUnit.DocTests/TUnit.DocTests.csproj b/tests/TUnit.DocTests/TUnit.DocTests.csproj index 8f74624afbb..3e94ef8315f 100644 --- a/tests/TUnit.DocTests/TUnit.DocTests.csproj +++ b/tests/TUnit.DocTests/TUnit.DocTests.csproj @@ -38,6 +38,7 @@ + @@ -45,6 +46,7 @@ + diff --git a/tools/TUnit.DocSnippetGenerator/Program.cs b/tools/TUnit.DocSnippetGenerator/Program.cs index 453a5c18428..23b9e524b28 100644 --- a/tools/TUnit.DocSnippetGenerator/Program.cs +++ b/tools/TUnit.DocSnippetGenerator/Program.cs @@ -436,6 +436,7 @@ static string GenerateSource( else { builder.AppendLine($"namespace {generatedNamespace};"); + AppendMockingLibraryUsings(builder, snippet); } builder.AppendLine($"#line {snippet.Line} \"{snippet.SourcePath}\"") @@ -504,6 +505,7 @@ static string GenerateSource( } builder.AppendLine($"namespace {generatedNamespace};"); + AppendMockingLibraryUsings(builder, snippet); if (containsExtensionMethod) { @@ -691,6 +693,36 @@ static void AppendFrameworkAliases(StringBuilder builder, Snippet snippet) } } +// Mocking-library migration pages compare Moq, NSubstitute, and FakeItEasy with TUnit.Mocks. +// Their type names (Mock, Times, MockBehavior, Arg) clash with TUnit.Mocks' global usings, +// so a fence marked with the library's name imports it inside the namespace, where lookup +// finds it before the global usings. +static void AppendMockingLibraryUsings(StringBuilder builder, Snippet snippet) +{ + if (!snippet.SourcePath.Contains("/migration/mocking/", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + var marker = Regex.Match(snippet.Source, @"(?m)^\s*//\s*(Moq|NSubstitute|FakeItEasy)\s*$"); + if (!marker.Success) + { + return; + } + + var namespaces = marker.Groups[1].Value switch + { + "Moq" => new[] { "Moq" }, + "NSubstitute" => new[] { "NSubstitute", "NSubstitute.ExceptionExtensions", "NSubstitute.ReceivedExtensions" }, + _ => new[] { "FakeItEasy" } + }; + + foreach (var ns in namespaces) + { + builder.AppendLine($"using {ns};"); + } +} + static string QualifyLocallyDeclaredAttributeTypes(string attribute, string source, string generatedNamespace) { var declaredTypes = Regex.Matches(