Skip to content

Overview

Creating an imposter

Define the target interface and enable generation:

Example

using Imposter.Abstractions;

[assembly: GenerateImposter(typeof(Imposter.Tests.Docs.Methods.IQuickStartService))]

public interface IQuickStartService
{
    int GetNumber();

    int Increment(int v);

    System.Threading.Tasks.Task<int> GetNumberAsync();

    System.Threading.Tasks.Task DoWorkAsync();

    int Combine(int a, int b);

    int VirtualCompute(int v);
}

Setup Return values

  • Return a constant value:

Example

imposter.GetNumber().Returns(42);

imposter.Instance().GetNumber(); // returns 42
  • Return via a delegate (captures inputs):

Example

imposter.Increment(Arg<int>.Any()).Returns(v => v + 2);

imposter.Instance().Increment(10); // returns 12;
  • Return a Span<T> or ReadOnlySpan<T>: Returns takes the array the returned span covers. Every call returns a span over that same array, so writes to a returned Span<T> land in it. A delegate passed to Returns returns the span itself.

Example

// Span<byte> Rent(int size);
var buffer = new byte[4];
imposter.Rent(Arg<int>.Any()).Returns(buffer);

var span = imposter.Instance().Rent(4); // a span over buffer
span[0] = 42; // buffer[0] is now 42
  • Return another ref struct: an imposter can't keep such a value, so Returns takes only a delegate, as in Returns(size => new Slot(size)), and the invocation history doesn't record the result. Without a setup the method returns default. A method's ref struct parameters are passed to the delegate the same way (see Ref struct parameters).
  • Sequence multiple outcomes with Then():

Example

imposter
     .Increment(Arg<int>.Any())
     .Returns(v => v + 2)
     .Then()
     .Returns(v => v + 3)
     .Then()
     .Returns(v => v + 4);

 imposter.Instance().Increment(10); // returns 12
 imposter.Instance().Increment(10); // returns 13
 imposter.Instance().Increment(10); // returns 14

 // sequence is exhausted, last outcome repeats
 imposter.Instance().Increment(10); // returns 14

Note

  • Use Then() to set up sequence
  • After a sequence is exhausted, the last outcome repeats

Async Methods

With async methods, imposter provides some handy methods to simplify setup:

  • Task and ValueTask methods:

Example

imposter.GetNumberAsync().ReturnsAsync(42);

await imposter.Instance().GetNumberAsync(); // returns 42
  • Task-returning (no result):

Example

imposter.DoWorkAsync().Returns(Task.CompletedTask);
  • Sequencing remains the same:

Example

imposter.GetNumberAsync()
    .ReturnsAsync(1)
    .Then().Returns(() => Task.FromResult(2));

Pro tip

Prefer ReturnsAsync for Task/ValueTask methods. It reads cleaner and avoids accidental sync-over-async in factories.

Warning

Async methods without setup return default, which in case of Task is null.

Ref/Out/In Parameters

Use OutArg<T>.Any() to match out parameters; Arg<T> for ref, in and ref readonly. Span parameters have matchers of their own; see Span parameters.

Returns and callbacks can specify out/ref/in in the delegate signature:

Example

imposter.GenericAllRefKind<int, string, double, bool, int>(
        OutArg<int>.Any(),
        Arg<string>.Any(),
        Arg<double>.Any(),
        Arg<bool[]>.Any())
    .Returns((out int o, ref string r, in double d, bool[] args) => { o = 5; return 99; })
    .Callback((out int o, ref string r, in double d, bool[] args) => { o = 5; });

Overloads that differ only in passing a parameter by reference

Arg<T> matches a parameter whether it's passed by value, in, ref or ref readonly, so overloads such as int Count(int value) and int Count(in int value) can't share a setup method. The overload declared first keeps the name and the others get a numbered one (Count_1), on the imposter and in its setup views:

Example

imposter.Count(Arg<int>.Any()).Returns(1); // int Count(int value)
imposter.Count_1(Arg<int>.Any()).Returns(2); // int Count(in int value)

var value = 5;
imposter.Instance().Count(value); // 1
imposter.Instance().Count(in value); // 2

Base Implementation (Class Targets)

Info

UseBaseImplementation() applies only to non-abstract, virtual class members. It is not available for interfaces. See the dedicated page: Base Implementation.

Verification

Use Called(Count.*) on the method to assert invocation counts:

Example

// After calling service methods
imposter.Increment(Arg<int>.Any()).Called(Count.AtLeast(2));
imposter.Increment(2).Called(Count.Once());

See more examples on GitHub. Count options: - Exactly(n), AtLeast(n), AtMost(n), Once(), Never(), Any

If verification fails, a VerificationFailedException is thrown with a clear message.

Pro tip

Place verification at the end of the test. Verify the broad call first (e.g., Any()), then the specific one to keep failure messages focused.

Overlapping setups

The newest matching setup with a configured outcome wins. Returns, ReturnsAsync, Throws, ThrowsAsync, Callback, and UseBaseImplementation configure outcomes. A newer bare setup, including one created for Called or CallCount, does not replace an older configured outcome.

When no configured setup matches, the newest bare match supplies the usual default behavior. In particular, a bare void setup still allows a call in Explicit mode. To override an older void Throws or Callback with a no-op, configure an empty callback explicitly:

imposter.Log(Arg<string>.Any()).Throws<InvalidOperationException>();
imposter.Log(Arg<string>.Is("health")).Callback(_ => { });

Concurrency Notes

While a new matching setup is being configured, calls can continue using an older configured setup. Creating a builder alone does not displace that outcome. An unfinished Then() step stays pending: calls repeat the previous outcome until the next step is configured. Use Then().Callback(...) with an empty callback for an explicit void no-op step.

If the first non-void setup is still pending, calls return the usual default in implicit mode or throw MissingImposterException in explicit mode. Those calls do not consume the pending setup; configuring it later still takes effect.

Sequenced outcomes are consumed in order under concurrency; the implementation avoids races that would otherwise reorder or drop planned outcomes. If a sequence is exhausted, the last outcome is repeated (when applicable) or default behavior applies.

Troubleshooting

  • Repeating Returns or Throws without Then() is invalid.
  • In Explicit mode, missing setups throw MissingImposterException.
  • Ensure OutArg<T>.Any() is used for out parameters; use Arg<T> for ref/in/ref readonly.