Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
69 changes: 29 additions & 40 deletions Docs/pages/00-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,23 @@
It enables fast, compile-time validated mocking with .NET Standard 2.0, .NET 8, .NET 10 and .NET Framework 4.8.

- **Source generator-based**: No runtime proxy generation.
- **Fast**: Direct dispatch with no reflection or dynamic proxies.
- **Strongly-typed**: Compile-time safety and IntelliSense support.
- **AOT compatible**: Works with Native AOT and trimming.
- **Modern C#**: First-class support for ref structs, static interface members, and current language features.

## Why Mockolate

| | Reflection-based mocks (Moq, NSubstitute, …) | Mockolate |
|---|---|---|
| AOT / trimming | not supported | supported |
| Validation | runtime exceptions | analyzers + compile errors |
| Setup API | `Expression<Func<…>>` trees | regular method calls |
| Hot path | dynamic-proxy dispatch | direct dispatch |

For side-by-side setup, usage, and verification syntax against Moq, NSubstitute, and FakeItEasy, see the [full code comparison](08-comparison.md).

Already on Moq? The companion package [`Mockolate.Migration`](https://github.com/aweXpect/Mockolate.Migration) ships analyzers and code fixers that translate common Moq patterns to Mockolate syntax in-place - point it at an existing test project and apply the suggested fixes.

## Getting Started

Expand All @@ -26,49 +41,23 @@ It enables fast, compile-time validated mocking with .NET Standard 2.0, .NET 8,
```csharp
using Mockolate;

public delegate void ChocolateDispensedDelegate(string type, int amount);
public interface IChocolateDispenser
{
int this[string type] { get; set; }
int TotalDispensed { get; set; }
bool Dispense(string type, int amount);
event ChocolateDispensedDelegate ChocolateDispensed;
}
// Create a mock of IChocolateDispenser

// Create a mock
IChocolateDispenser sut = IChocolateDispenser.CreateMock();

// Setup: Initial stock of 10 for Dark chocolate
sut.Mock.Setup["Dark"].InitializeWith(10);
// Setup: Dispense decreases Dark chocolate if enough, returns true/false
sut.Mock.Setup.Dispense("Dark", It.IsAny<int>())
.Returns((type, amount) =>
{
int current = sut[type];
if (current >= amount)
{
sut[type] = current - amount;
sut.Mock.Raise.ChocolateDispensed(type, amount);
return true;
}
return false;
});

// Track dispensed amount via event
int dispensedAmount = 0;
sut.ChocolateDispensed += (type, amount) =>
{
dispensedAmount += amount;
};

// Act: Try to dispense chocolates
bool gotChoc1 = sut.Dispense("Dark", 4); // true
bool gotChoc2 = sut.Dispense("Dark", 5); // true
bool gotChoc3 = sut.Dispense("Dark", 6); // false

// Verify: Check interactions
sut.Mock.Verify.Dispense("Dark", It.IsAny<int>()).Exactly(3);

// Output: "Dispensed amount: 9. Got chocolate? True, True, False"
Console.WriteLine($"Dispensed amount: {dispensedAmount}. Got chocolate? {gotChoc1}, {gotChoc2}, {gotChoc3}");

// Setup: Dispense returns true for any Dark chocolate request
sut.Mock.Setup.Dispense("Dark", It.IsAny<int>()).Returns(true);

// Act
bool success = sut.Dispense("Dark", 4);

// Verify
sut.Mock.Verify.Dispense("Dark", It.IsAny<int>()).Once();
```

For a richer walkthrough combining properties, indexers, events, and stateful setup,
see [A complete example](09-complete-example.md).
Comment thread
vbreuss marked this conversation as resolved.
63 changes: 29 additions & 34 deletions Docs/pages/01-create-mocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,40 +28,35 @@ MyChocolateDispenser classMock = MyChocolateDispenser.CreateMock(behavior, "Dark

**`MockBehavior` options**

- `SkipBaseClass` (bool):
- If `false` (default), the mock will call the base class implementation and use its return values as default
values, if no explicit setup is defined.
- If `true`, the mock will not call any base class implementations.
- `ThrowWhenNotSetup` (bool):
- If `false` (default), the mock will return a default value (see `DefaultValue`), when no matching setup is found.
- If `true`, the mock will throw an exception when no matching setup is found.
- `SkipInteractionRecording` (bool):
- If `false` (default), every interaction with the mock is recorded and can be verified later.
- If `true`, the mock skips recording interactions for faster execution.
Setups, returns, callbacks and base-class delegation continue to work - only verification is disabled. Any
attempt to call `.Verify.X()` throws a `MockException`.
- `DefaultValue` (IDefaultValueGenerator):
- Customizes how default values are generated for methods/properties that are not set up.
- The default implementation provides sensible defaults for the most common use cases:
- Empty collections for collection types (e.g., `IEnumerable<T>`, `List<T>`, etc.)
- Empty string for `string`
- Completed tasks for `Task`, `Task<T>`, `ValueTask` and `ValueTask<T>`
- Tuples with recursively defaulted values
- `null` for other reference types
- You can add custom default value factories for specific types using `.WithDefaultValueFor<T>()`:
```csharp
MockBehavior behavior = MockBehavior.Default
.WithDefaultValueFor<string>(() => "default")
.WithDefaultValueFor<int>(() => 42);
IChocolateDispenser sut = IChocolateDispenser.CreateMock(behavior);
```
This is useful when you want mocks to return specific default values for certain types instead of the standard
defaults.
- `Initialize<T>(params Action<IMockSetup<T>>[] setups)`:
- Automatically initialize all mocks of type T with the given setups when they are created.
- `UseConstructorParametersFor<T>(object?[])`:
- Configures constructor parameters to use when creating mocks of type `T`, unless explicit parameters are provided
during mock creation via `CreateMock([…])`.
| Option | Default | Purpose |
|---|---|---|
| `SkipBaseClass` | `false` | When `true`, the mock does not call any base class implementations. Otherwise, the base class implementation is used as the default value when no explicit setup matches. |
| `ThrowWhenNotSetup` | `false` | When `true`, the mock throws when no matching setup is found. Otherwise, it returns a default value (see `DefaultValue` below). |
| `SkipInteractionRecording` | `false` | When `true`, interactions are not recorded - setups, returns, callbacks, and base-class delegation still work, but `.Verify.X()` throws a `MockException`. Useful in performance-sensitive scenarios. |
| `DefaultValue` | sensible defaults | Customizes how default values are generated for unset methods and properties (see below). |
| `Initialize<T>(...)` | - | Automatically applies the given setups to all mocks of type `T` when they are created. |
| `UseConstructorParametersFor<T>(...)` | - | Configures default constructor parameters for mocks of type `T`, unless explicit parameters are supplied to `CreateMock([…])`. The `Func<object?[]>` overload defers parameter resolution until each mock is created. |

**Default value generation**

The default `IDefaultValueGenerator` provides sensible defaults for the most common cases:

- Empty collections for collection types (e.g., `IEnumerable<T>`, `List<T>`)
- Empty string for `string`
- Completed tasks for `Task`, `Task<T>`, `ValueTask`, and `ValueTask<T>`
- Tuples with recursively defaulted values
- `null` for other reference types

You can register custom factories per type using `.WithDefaultValueFor<T>()`:

```csharp
MockBehavior behavior = MockBehavior.Default
.WithDefaultValueFor<string>(() => "default")
.WithDefaultValueFor<int>(() => 42);
IChocolateDispenser sut = IChocolateDispenser.CreateMock(behavior);
```

For full control, implement `IDefaultValueGenerator` directly and assign it to `MockBehavior.DefaultValue`.

**Using a shared behavior**

Expand Down
2 changes: 1 addition & 1 deletion Docs/pages/07-analyzers.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ framework and/or `<LangVersion>` to resolve it.
These fire on every compilation target, including .NET 9+ / C# 13+:

- Parameters marked `out`, `ref`, or `ref readonly` whose type is a non-`Span<T>` /
non-`ReadOnlySpan<T>` ref struct the mock can't round-trip the value through
non-`ReadOnlySpan<T>` ref struct - the mock can't round-trip the value through
`IOutParameter<T>` / `IRefParameter<T>` when `T` is a ref struct.
- Methods returning a non-`Span<T>` / non-`ReadOnlySpan<T>` ref struct.

Expand Down
4 changes: 2 additions & 2 deletions Docs/pages/setup/04-parameter-matching.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ or your own `ref struct Packet`) using these matchers:
predicate can read the struct's fields at the time the call is made.
- `It.IsRefStructBy<T, TKey>(projection)` / `It.IsRefStructBy<T, TKey>(projection, predicate)`:
For ref-struct-keyed indexers, projects the key to an equatable value so writes and reads can
be correlated. Works at any arity apply it to every ref-struct slot and non-ref-struct slots
be correlated. Works at any arity - apply it to every ref-struct slot and non-ref-struct slots
contribute their raw value to the composite dispatch key (see *Indexer storage* in the remarks).

```csharp
Expand Down Expand Up @@ -196,7 +196,7 @@ generic delegates:
`Callbacks<T>` builder (`InParallel`, `When`, `For`, `Only`, `TransitionTo`) are not offered
for ref-struct parameters.
- **Verify.** `Verify` counts calls to the method but cannot match on the parameter value after
the fact the ref-struct value isn't retained past the call. Use a setup-time matcher to
the fact - the ref-struct value isn't retained past the call. Use a setup-time matcher to
filter at call time.
- **Indexer storage.** By default, values written through a ref-struct-keyed indexer setter are
not read back by the getter. Apply `It.IsRefStructBy<T, TKey>(projection)` to every ref-struct
Expand Down
Loading