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.
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
ReturnsorThrowswithoutThen()is invalid. - In
Explicitmode, missing setups throwMissingImposterException. - Ensure
OutArg<T>.Any()is used foroutparameters; useArg<T>forref/in/ref readonly.