Refactoring conditional setups
I noticed in the previous post that neither MockSequence
nor an earlier
version of mock.SetupSequence
were thread-safe, due to the way how
they were implemented using conditional setups.
Let’s start by quickly reviewing conditional setups.
What are they?
Conditional setups are setups that only get matched when some external
condition is met. You set them up using mock.When
:
var env = "quux";
var mock = new Mock<ISomeInterface>();
mock.When(() => env == "foo").Setup(m => m.SomeMethod()).Callback(() => DoFoo());
mock.When(() => env == "bar").Setup(m => m.SomeMethod()).Callback(() => DoBar());
env = "bar";
mock.Object.SomeMethod(); // second setup will match
env = "foo";
mock.Object.SomeMethod(); // first setup will match
(I’m using the variable name env
, short for “environment”, to hint at
the fact that setup conditions always test state that lives outside of
the setups.)
Conditional setups are how all things “sequence” used to be implemented. You’d typically have an integer counter that keeps track of how far you have advanced the sequence. Each time a setup in the sequence gets matched, you’d have to increase that counter.
Simplifying the implementation behind conditional setups
The condition passed to mock.When
ends up being wrapped internally by
the Condition
class. Each Setup
can have a Condition
instance
associated with it.
Condition
does not wrap just that predicate; it also supports a
success
delegate that gets invoked whenever the associated setup has
matched an invocation and is about to be executed. This delegate was
where the sequence counter used to be increased for MockSequence
s.
It turns out that this was the only case where success
was used.
Let’s verify this by removing it and recompiling to see what breaks:
internal sealed class Condition
{
...
- public Condition(Func<bool> condition, Action success = null)
+ public Condition(Func<bool> condition)
{
this.condition = condition;
- this.success = success;
+ this.success = null;
}
...
}
And it turns out that nothing breaks, meaning that success
is no
longer used anywhere. This class has essentially become obsolete, and
it can be replaced with the Func<bool>
that it contains.
I’ve done this refactoring in 2ba2fd9
.
Why does it matter?
Apart from making setups a little lighter (one less object reference),
this refactoring also removes an potential place from which potential
thread-safety issues can arise. No longer having the option to increase
a sequence counter in Condition
means we will need to look for another
(and hopefully better) solution if and when we want to reintroduce
sequences!