Imposter Documentation

repository·master·Indexed 20 days ago

https://github.com/themidnightgospel/imposter

A high-performance, strongly-typed mocking library for .NET designed for speed and developer experience. Imposter supports mocking interfaces and non-sealed classes, including methods, properties, indexers, and events. It features advanced argument matching via the Arg<T> API, support for ref/out parameters, and the ability to forward calls to base implementations using UseBaseImplementation().

Tokens
25.3K
Snippets
100
Records
121
Agent score
70%

What's inside Imposter

  1. Imposter capabilities overview

    master

    Imposter is a high-performance mocking library for .NET that supports a wide range of impersonation scenarios:

    • Members: Methods, Properties, Indexers, and Events.
    • Types: Interfaces and non-sealed classes (including protected members).
    • Features: Full generic support, Async support, and thread-safe design for parallel test environments.
    • Typing: Strongly typed throughout the entire mocking pipeline to prevent runtime type mismatch exceptions.
  2. How sequential outcomes behave under concurrency

    master

    When a mocked method is called concurrently, the outcomes in a sequence are consumed in the order they were defined.

    Exhaustion Behavior: Once the sequence of defined outcomes is exhausted, the method will fall back to either the last applicable outcome in the sequence or to the default behavior, depending on the target type and the current Imposter mode.

  3. Async behavior and outcome sequencing

    master

    Imposter handles asynchronous methods with the following behaviors:

    • Default Returns: Async methods that do not have a defined setup will return default. For Task types, this means they return null.
    • Sequence Exhaustion: When using sequenced async outcomes, they are consumed in order. If the sequence is exhausted, the last outcome in the sequence is repeated (where applicable).
  4. How type matching works for generics

    master

    Imposter applies specific matching rules for different parameter types when working with generics:

    1. Input Parameters: Setups on a generic argument type (e.g., Animal) match calls made with that type or any derived type (e.g., Cat). However, a setup on a derived type (Cat) will not match a call made with a base type (Animal).
    2. Output Parameters: Setups on a derived type (Cat) can be invoked even if the method is called using a base type (IAnimal) as the output parameter. The value is assigned and observable through the base-typed variable.
    3. Ref Parameters: Matching is strict based on the exact static generic argument. A setup on a base type (IAnimal) does not match a ref argument of a derived type (Cat).
    4. Generic Return Types: Setups for a specific derived type (Cat) work when the method is invoked with a base return type (IAnimal). Conversely, setups for a base type (IAnimal) will not be used when the method is invoked with a derived return type (Cat), resulting in a default value.
  5. Behavior of methods in Implicit and Explicit modes

    master

    When using ImposterMode, the behavior of methods depends on whether a setup has been defined using .Returns() or .ReturnsAsync().

    Implicit Mode: Returns default(T) for any method call without a setup.

    Explicit Mode: Throws MissingImposterException for any method call without a setup.

    Example setup for both modes:

    // Implicit behavior
    var imposter = new IMyServiceImposter(ImposterMode.Implicit);
    var service = imposter.Instance();
    int n = service.GetNumber(); // Returns 0
    
    imposter.GetNumber().Returns(42);
    service.GetNumber(); // Returns 42
    
    // Explicit behavior
    var imposter = new IMyServiceImposter(ImposterMode.Explicit);
    var service = imposter.Instance();
    // service.GetNumber(); // Throws MissingImposterException
    
    imposter.GetNumber().Returns(42);
    service.GetNumber(); // Returns 42
    using Imposter.Abstractions;
    
    [assembly: GenerateImposter(typeof(IMyService))]
    
    public interface IMyService
    {
        int GetNumber();
        System.Threading.Tasks.Task<int> GetNumberAsync();
    }
  6. Understand ImposterMode: Implicit vs Explicit

    master

    Imposter provides two modes via the ImposterMode enum that determine how the generated imposter behaves when a method, property, or indexer is called without a prior setup.

    Implicit Mode

    In ImposterMode.Implicit, calls to members without setups are handled gracefully:

    • Methods: Return default(T).
    • Properties: Getters return default(T); setters are ignored (do nothing).
    • Indexers: Getters return default(T); setters are ignored.
    • Best Use Case: Use this during prototyping or spikes where receiving default values is acceptable and you want to avoid boilerplate setups.

    Explicit Mode

    In ImposterMode.Explicit, calls to members without setups are treated as errors:

    • Methods, Properties, and Indexers: Any access (get, set, or call) that lacks a corresponding setup throws a MissingImposterException.
    • Best Use Case: Use this as the default for unit tests to ensure all interactions with the dependency are intentionally configured and to catch missing setups early.
  7. Configure Imposter Mode (Strict vs Loose)

    master

    You can control how Imposter behaves when a method or property is called without a prior setup via ImposterMode:

    • Implicit (Loose): Missing setups return default values. This is the default behavior.
    • Explicit (Strict): Missing setups throw a MissingImposterException. Use this mode to ensure all interactions are explicitly defined in your tests.
  8. Handle exceptions in Callbacks

    master

    If a callback throws an exception, the exception is propagated to the caller. Crucially, the exception is thrown after the method result has already been produced and returned to the caller. This means the caller receives the expected return value, and then the exception is raised.

    imposter.GetNumber()
        .Returns(1)
        .Callback(() => throw new InvalidOperationException("boom"));
    
    // service.GetNumber() returns 1, then throws InvalidOperationException
  9. Understand Implicit vs Explicit modes

    master

    Imposter allows you to control how unmocked members (members that have not been explicitly set up with a return value) behave:

    • Implicit Mode: Returns default values silently. This is useful for rapid prototyping where you don't want to set up every single interaction.
    • Explicit Mode: Throws an exception when any unmocked member is called. This is ideal for strict unit testing to ensure your tests are precise and only interact with expected behaviors.
  10. Use Open Generics to register generic interfaces or classes

    master

    Open generics allow you to register a single generic interface or class (using the open generic syntax typeof(T<>)) and automatically generate imposters for any concrete type used at the call site.

    Key behaviors of Open Generics:

    • Isolation: Each closed generic type (e.g., IAsyncObservable<string> vs IAsyncObservable<int>) receives its own unique imposter type, setup, and call tracking history. They do not share state.
    • Scoped Verification: Verification is scoped to both the specific concrete type argument and the specific imposter instance. Calls made to an int instance will not satisfy verification requirements for a string imposter.
    • Member Independence: Isolation applies to all members (methods, properties, indexers, and events) of the generic type.
    // Register the open generic type
    [assembly: GenerateImposter(typeof(IAsyncObservable<>))]
    
    public interface IAsyncObservable<T>
    {
        void OnNext(T item);
    }
  11. Impersonation constraints for class targets

    master

    When using Imposter to impersonate classes, observe these restrictions:

    • Class Modifiers: Only non-sealed classes can be impersonated.
    • Member Modifiers: Only virtual or abstract members can be impersonated on class imposters.
    • Base Implementation: The UseBaseImplementation() method is only applicable to members that are both non-abstract and virtual. It cannot be used for interface members.
  12. Behavior of properties and indexers in Implicit and Explicit modes

    master

    Properties and indexers follow the same logic regarding ImposterMode:

    FeatureImplicit Mode BehaviorExplicit Mode Behavior
    Property GetterReturns default(T)Throws MissingImposterException
    Property SetterIgnored (does nothing)Throws MissingImposterException
    Indexer GetterReturns default(T)Throws MissingImposterException
    Indexer SetterIgnored (does nothing)Throws MissingImposterException

    Implicit Example:

    var imposter = new IPropertySetupSutImposter(ImposterMode.Implicit);
    var sut = imposter.Instance();
    
    int n = sut.Age; // 0 (default)
    sut.Age = 10;    // Ignored
    
    int value = sut[1, "key"]; // 0 (default)
    sut[1, "key"] = 10;       // Ignored

    Explicit Example:

    var imposter = new IPropertySetupSutImposter(ImposterMode.Explicit);
    var sut = imposter.Instance();
    
    // sut.Age;       // Throws MissingImposterException
    // sut.Age = 10;  // Throws MissingImposterException
    
    // var value = sut[1, "missing"]; // Throws MissingImposterException
    // sut[1, "missing"] = 10;       // Throws MissingImposterException