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
76 changes: 76 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

### Primary Commands

- `dotnet run --project build/Build` - Main build command that restores, builds the solution
- `dotnet run --project build/Build -- --target=Test` - Run unit tests with coverage
- `dotnet run --project build/Build -- --target=Package` - Build and package for NuGet
- `dotnet run --project build/Publish -- --target=PublishNuGet` - Publish to NuGet (CI only)

### Testing Commands

- `dotnet test src/Mimic.UnitTests/Mimic.UnitTests.csproj` - Run tests directly
- `dotnet test src/Mimic.UnitTests/Mimic.UnitTests.csproj --framework net8.0` - Test specific framework
- `dotnet test src/Mimic.UnitTests/Mimic.UnitTests.csproj --framework net9.0` - Test on .NET 9

## Project Structure

### Core Architecture

Mimic is a .NET mocking library built on Castle DynamicProxy with a fluent API design:

- **Main Library** (`src/Mimic/`): The core mocking functionality
- `Mimic<T>` - Main generic mimic class for creating mock objects
- `Setup/` - Fluent API for configuring method/property behaviours
- `Proxy/` - Castle DynamicProxy integration for runtime proxy generation
- `Expressions/` - Expression tree handling for type-safe setup syntax

- **Test Project** (`src/Mimic.UnitTests/`): Comprehensive unit tests
- Multi-target framework support (net8.0, net9.0)
- Uses xUnit, Shouldly, and AutoFixture
- Organised by feature area (Setup/, Expressions/, Core/, etc.)

### Key Components

- **Mimic<T>**: Generic wrapper providing fluent API for mock configuration
- **Setup System**: Expression-based method/property setup with behaviours
- **Proxy Generation**: Runtime proxy creation using Castle DynamicProxy
- **Argument Matching**: Type-safe argument matchers (`Arg.Any<T>()`, etc.)
- **Verification**: Post-execution verification of method calls

### Build System

Custom Cake-based build system in `build/` directory:

- **Build Project**: Main build tasks (Clean, Build, Test, Package)
- **Publish Project**: NuGet publishing tasks
- **Common**: Shared utilities and models
- Uses GitVersion for semantic versioning
- Coverlet for code coverage with Codecov integration

## Development Guidelines

### Testing Framework

- Primary: xUnit with Shouldly assertions
- Test data: AutoFixture for test data generation
- Coverage: Coverlet with 80%+ target coverage
- Multi-framework testing on net8.0 and net9.0

### Code Organisation

- Follow existing namespace patterns (Mimic.Core, Mimic.Setup, etc.)
- Use fluent interface patterns for public APIs
- Internal classes use Castle DynamicProxy conventions
- Expression trees for type-safe configuration

### Package Management

- Central Package Management via Directory.Packages.props
- Package lock files are enabled for reproducible builds
- Castle.Core dependency for proxy generation
- JetBrains.Annotations for code contracts
21 changes: 21 additions & 0 deletions src/Mimic/Exceptions/MimicException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,38 @@

namespace Mimic.Exceptions;

/// <summary>
/// Represents errors that occur during Mimic library operations. This is the primary exception type thrown by
/// Mimic to indicate various categories of errors such as usage errors, incompatible types, unsupported expressions,
/// and expectation failures.
/// </summary>
[PublicAPI]
public class MimicException : Exception
{
/// <summary>
/// Gets the reason category that describes the type of mimic-related error that occurred.
/// </summary>
/// <value>A <see cref="Mimic.Exceptions.Reason"/> value indicating the category of error.</value>
public Reason Reason { get; }

/// <summary>
/// Initialises a new instance of the <see cref="MimicException"/> class with the specified reason and message.
/// </summary>
/// <param name="reason">The reason category that describes the type of error.</param>
/// <param name="message">The message that describes the error, or <c>null</c> to use a default message.</param>
public MimicException(Reason reason, string? message)
: base(message)
{
Reason = reason;
}

/// <summary>
/// Initialises a new instance of the <see cref="MimicException"/> class with the specified reason, message,
/// and inner exception.
/// </summary>
/// <param name="reason">The reason category that describes the type of error.</param>
/// <param name="message">The message that describes the error, or <c>null</c> to use a default message.</param>
/// <param name="innerException">The exception that is the cause of the current exception or <c>null</c> if no inner exception is specified.</param>
public MimicException(Reason reason, string? message, Exception? innerException)
: base(message, innerException)
{
Expand Down
64 changes: 64 additions & 0 deletions src/Mimic/Extensions/DelayableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
namespace Mimic;

/// <summary>
/// Provides extension methods for adding random delays to mimic setups, enhancing the realism of mock behaviours by introducing timing variability.
/// </summary>
[PublicAPI]
public static class DelayableExtensions
{
#region IDelayable

/// <summary>
/// Adds a random delay between the specified minimum and maximum values to the mimic setup using <see cref="Random.Shared"/>.
/// </summary>
/// <param name="mimic">The mimic instance to add the delay to.</param>
/// <param name="minDelay">The minimum delay duration.</param>
/// <param name="maxDelay">The maximum delay duration.</param>
/// <returns>An <see cref="IDelayableResult"/> that can be used to continue configuring the mimic setup.</returns>
/// <exception cref="Guard.AssertionException">Thrown when <paramref name="minDelay"/> is not less than <paramref name="maxDelay"/>.</exception>
/// <remarks>
/// This method uses <see cref="Random.Shared"/> for generating random delays, making it suitable for most scenarios.
/// For deterministic testing or when you need control over the randomisation, use the overload that accepts a <see cref="Random"/> parameter.
/// </remarks>
public static IDelayableResult WithDelay(this IDelayable mimic, TimeSpan minDelay, TimeSpan maxDelay)
{
return mimic.WithDelay(minDelay, maxDelay, Random.Shared);
}

/// <summary>
/// Adds a random delay between the specified minimum and maximum values to the mimic setup using the provided random number generator.
/// </summary>
/// <param name="mimic">The mimic instance to add the delay to.</param>
/// <param name="minDelay">The minimum delay duration.</param>
/// <param name="maxDelay">The maximum delay duration.</param>
/// <param name="random">The random number generator to use for generating delays.</param>
/// <returns>An <see cref="IDelayableResult"/> that can be used to continue configuring the mimic setup.</returns>
/// <exception cref="Guard.AssertionException">Thrown when <paramref name="minDelay"/> is not less than <paramref name="maxDelay"/>.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="random"/> is <c>null</c>.</exception>
/// <remarks>
/// This overload allows you to provide your own <see cref="Random"/> instance, which is useful for deterministic testing
/// or when you need to control the seed for reproducible random delays.
/// </remarks>
public static IDelayableResult WithDelay(this IDelayable mimic, TimeSpan minDelay, TimeSpan maxDelay, Random random)
{
Guard.Assert(minDelay < maxDelay, $"{nameof(minDelay)} must be less than {nameof(maxDelay)}");
Expand All @@ -22,11 +51,39 @@ public static IDelayableResult WithDelay(this IDelayable mimic, TimeSpan minDela

#region ISequenceDelayable

/// <summary>
/// Adds a random delay between the specified minimum and maximum values to the sequence mimic setup using <see cref="Random.Shared"/>.
/// </summary>
/// <param name="mimic">The sequence Mimic instance to add the delay to.</param>
/// <param name="minDelay">The minimum delay duration.</param>
/// <param name="maxDelay">The maximum delay duration.</param>
/// <returns>An <see cref="IExpected"/> that can be used to continue configuring the sequence mimic setup.</returns>
/// <exception cref="Guard.AssertionException">Thrown when <paramref name="minDelay"/> is not less than <paramref name="maxDelay"/>.</exception>
/// <remarks>
/// This method uses <see cref="Random.Shared"/> for generating random delays, making it suitable for most scenarios.
/// For deterministic testing or when you need control over the randomisation, use the overload that accepts a <see cref="Random"/> parameter.
/// This overload is specifically designed for sequence-based mimic setups where the delay applies to sequential calls.
/// </remarks>
public static IExpected WithDelay(this ISequenceDelayable mimic, TimeSpan minDelay, TimeSpan maxDelay)
{
return mimic.WithDelay(minDelay, maxDelay, Random.Shared);
}

/// <summary>
/// Adds a random delay between the specified minimum and maximum values to the sequence mimic setup using the provided random number generator.
/// </summary>
/// <param name="mimic">The sequence Mimic instance to add the delay to.</param>
/// <param name="minDelay">The minimum delay duration.</param>
/// <param name="maxDelay">The maximum delay duration.</param>
/// <param name="random">The random number generator to use for generating delays.</param>
/// <returns>An <see cref="IExpected"/> that can be used to continue configuring the sequence mimic setup.</returns>
/// <exception cref="Guard.AssertionException">Thrown when <paramref name="minDelay"/> is not less than <paramref name="maxDelay"/>.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="random"/> is <c>null</c>.</exception>
/// <remarks>
/// This overload allows you to provide your own <see cref="Random"/> instance, which is useful for deterministic testing
/// or when you need to control the seed for reproducible random delays.
/// This method is specifically designed for sequence-based mimic setups where the delay applies to sequential calls.
/// </remarks>
public static IExpected WithDelay(this ISequenceDelayable mimic, TimeSpan minDelay, TimeSpan maxDelay, Random random)
{
Guard.Assert(minDelay < maxDelay, $"{nameof(minDelay)} must be less than {nameof(maxDelay)}");
Expand All @@ -37,6 +94,13 @@ public static IExpected WithDelay(this ISequenceDelayable mimic, TimeSpan minDel

#endregion

/// <summary>
/// Generates a random <see cref="TimeSpan"/> between the specified minimum and maximum delays.
/// </summary>
/// <param name="random">The random number generator to use.</param>
/// <param name="minDelay">The minimum delay duration.</param>
/// <param name="maxDelay">The maximum delay duration.</param>
/// <returns>A random <see cref="TimeSpan"/> between <paramref name="minDelay"/> and <paramref name="maxDelay"/>.</returns>
private static TimeSpan GetRandomDelay(Random random, TimeSpan minDelay, TimeSpan maxDelay) =>
new(random.Next((int)minDelay.Ticks, (int)maxDelay.Ticks));
}
Loading
Loading