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>orReadOnlySpan<T>:Returnstakes the array the returned span covers. Every call returns a span over that same array, so writes to a returnedSpan<T>land in it. A delegate passed toReturnsreturns 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, soReturnstakes only a delegate, as inReturns(size => new Slot(size)), and the invocation history doesn't record the result. Without a setup the method returnsdefault. A method'sref structparameters 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.
Next steps
- Deep dives for methods:
- Sequential Returns
- Throwing Exceptions
- Verification
- Callbacks
- Base Implementation
- Protected Methods
- Imposter Modes
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.
A bare setup never answers a call. When no configured setup matches, the call gets the usual default behavior in Implicit mode and throws MissingImposterException in Explicit mode, even for a void method. To allow a void call in Explicit mode, or to override an older void Throws or Callback with a no-op, configure an empty callback:
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
ReturnsorThrowswithoutThen()is invalid. - In
Explicitmode, missing setups throwMissingImposterException. - Ensure
OutArg<T>.Any()is used foroutparameters; useArg<T>forref/in/ref readonly.