diff --git a/Docs/pages/00-index.md b/Docs/pages/00-index.md index be780133..c5ccd651 100644 --- a/Docs/pages/00-index.md +++ b/Docs/pages/00-index.md @@ -62,4 +62,4 @@ at an existing test project and apply the suggested fixes. ``` For a richer walkthrough combining properties, indexers, events, and stateful setup, - see [A complete example](09-complete-example.md). + see [A complete example](10-complete-example.md). diff --git a/Docs/pages/09-benchmarks.mdx b/Docs/pages/09-benchmarks.mdx new file mode 100644 index 00000000..dd4e7dcb --- /dev/null +++ b/Docs/pages/09-benchmarks.mdx @@ -0,0 +1,150 @@ +--- +title: Benchmarks +sidebar_position: 9 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import MockolateBenchmarkResult from '@site/src/components/MockolateBenchmarkResult'; + +These benchmarks measure Mockolate's runtime overhead against +[Moq](https://github.com/devlooped/moq), +[NSubstitute](https://nsubstitute.github.io/), +[FakeItEasy](https://fakeiteasy.github.io/), +[TUnit.Mocks](https://github.com/thomhurst/TUnit/) and +[Imposter](https://github.com/themidnightgospel/Imposter) for the same end-to-end mocking flow. + +:::info[Methodology] + +- **Suite**: [`Mockolate.Benchmarks`](https://github.com/Testably/Mockolate/tree/main/Benchmarks/Mockolate.Benchmarks) driven by [BenchmarkDotNet](https://benchmarkdotnet.org/). +- **Job**: `MediumRun` with the in-process toolchain. +- **Runner**: `ubuntu-latest` GitHub Actions agent, refreshed on every push to `main`. +- Each row shows time and allocated memory, sorted fastest-first; bars are scaled within the row so length corresponds to magnitude. The Mockolate row is highlighted as the baseline (`1.00x`) and every other row shows its ratio relative to Mockolate. **Lower is better.** +- Benchmarks parameterised with `[Params(1, 10)]` (Method/Property/Indexer) repeat their inner body 1× or 10× to surface fixed-cost vs per-call overhead — switch between them with the inline tabs. + +::: + + + + + +Setup a method, call it `N` times with `It.IsAny()`, then verify it ran exactly `N` times. + + + +```csharp title="Mockolate" +IMyMethodInterface sut = IMyMethodInterface.CreateMock(); +sut.Mock.Setup.MyFunc(It.IsAny()).Returns(true); + +for (int i = 0; i < N; i++) + sut.MyFunc(42); + +sut.Mock.Verify.MyFunc(It.IsAny()).Exactly(N); +``` + +[View source on GitHub →](https://github.com/Testably/Mockolate/blob/main/Benchmarks/Mockolate.Benchmarks/CompleteMethodBenchmarks.cs) + + + + + +Initialise an `int` property to 42, read+write it `N` times, then verify the getter and setter each ran exactly `N` times. + + + +```csharp title="Mockolate" +IMyPropertyInterface sut = IMyPropertyInterface.CreateMock(); +sut.Mock.Setup.Counter.InitializeWith(42); + +for (int i = 0; i < N; i++) +{ + _ = sut.Counter; + sut.Counter = i; +} + +sut.Mock.Verify.Counter.Got().Exactly(N); +sut.Mock.Verify.Counter.Set(It.IsAny()).Exactly(N); +``` + +[View source on GitHub →](https://github.com/Testably/Mockolate/blob/main/Benchmarks/Mockolate.Benchmarks/CompletePropertyBenchmarks.cs) + + + + + +Setup a `string this[int]` indexer, read+write it `N` times, then verify the getter and setter each ran exactly `N` times. + + + +```csharp title="Mockolate" +IMyIndexerInterface sut = IMyIndexerInterface.CreateMock(); +sut.Mock.Setup[It.IsAny()].Returns("foo"); + +for (int i = 0; i < N; i++) +{ + _ = sut[42]; + sut[42] = "bar"; +} + +sut.Mock.Verify[It.IsAny()].Got().Exactly(N); +sut.Mock.Verify[It.IsAny()].Set(It.IsAny()).Exactly(N); +``` + +[View source on GitHub →](https://github.com/Testably/Mockolate/blob/main/Benchmarks/Mockolate.Benchmarks/CompleteIndexerBenchmarks.cs) + + + + + +Subscribe to a mock event, raise it once, then verify the subscription was recorded. + + + +```csharp title="Mockolate" +IMyEventInterface sut = IMyEventInterface.CreateMock(); +EventHandler handler = (_, _) => { }; + +sut.SomeEvent += handler; +sut.Mock.Raise.SomeEvent(null, EventArgs.Empty); + +sut.Mock.Verify.SomeEvent.Subscribed().Once(); +``` + +[View source on GitHub →](https://github.com/Testably/Mockolate/blob/main/Benchmarks/Mockolate.Benchmarks/CompleteEventBenchmarks.cs) + + + + + +Configure a callback on a method invocation and trigger it twice — measures the per-invocation cost of side-effect setups. + + + +```csharp title="Mockolate" +int count = 0; +IMyCallbackInterface sut = IMyCallbackInterface.CreateMock(); +sut.Mock.Setup.MyFunc(It.IsAny()).Do(() => count++); + +sut.MyFunc(1); +sut.MyFunc(2); +``` + +[View source on GitHub →](https://github.com/Testably/Mockolate/blob/main/Benchmarks/Mockolate.Benchmarks/CallbackBenchmarks.cs) + + + + + +Cost of constructing an empty mock — no setup, no invocations, no verification. The fixed overhead every test pays. + + + +```csharp title="Mockolate" +ICalculatorService.CreateMock(); +``` + +[View source on GitHub →](https://github.com/Testably/Mockolate/blob/main/Benchmarks/Mockolate.Benchmarks/MockCreationBenchmarks.cs) + + + + diff --git a/Docs/pages/09-complete-example.md b/Docs/pages/10-complete-example.md similarity index 100% rename from Docs/pages/09-complete-example.md rename to Docs/pages/10-complete-example.md