There are many isolation frameworks around that make writing unit tests relatively simple. It's highly unlikely that any single framework will meet all your needs, so writing your own utilities can be useful in order to make your tests easier to write and – perhaps more importantly – read.
I regularly find myself wanting to verify that a certain dependency is called in the right way by my system under test. This includes passed argument values, which can be complex objects themselves.
Suppose I want to verify that my fictitious SUT Circle.CalculateArea calls ICalculator.Square with the radius of the circle. This would be pretty easy with FakeItEasy:
var calculator = A.Fake<ICalculator>();
A.CallTo(() => calculator.Square(3)).Returns(9);
var sut = new Circle(3, calculator);
double area = sut.CalculateArea();
Assert.AreEqual(9d, area);
Now imagine that this parameter value is a whole lot more complicated than a double. FakeItEasy provides a syntax for that too. Say I want to verify that the product being saved has the right name:
A.CallTo(
() => repository.AddProduct(A<Product>.That.Matches(p => p.Name == "MyName")).
Returns(productId);
Still pretty readable. However, you can imagine if the number of properties to verify grows, this becomes increasingly difficult to read. I prefer to use Assert-methods from my unit testing framework instead. While it's perfectly possible to put these statements into a method and use it when I configure my fake, it's starting to get messy:
A.CallTo(
() => repository.AddProduct(A<Product>.That.Matches(p =>
{
Assert.AreEqual("MyName", p.Name);
Assert.AreEqual(100m, p.Price);
// Etcetera
return true;
}).
Returns(productId);
A better solution: capturing the argument value
Rather than using this syntax, you can capture the value passed for the parameter instead. This makes it easier to write a test that follows Arrange-Act-Assert more closely as well. Using some background magic with a helper class it can be pretty easy to read:
// Arrange
const int productId = 42;
var sourceProduct = new Product { Name = "Source Product", Price = 100m }
var productArg = new Capture<Product>();
A.CallTo(() => repository.AddProduct(productArg).Returns(productId);
// Act
sut.DuplicateProduct(sourceProduct);
// Assert
var copy = productArg.Value;
Assert.AreEqual("Copy of Source Product", product.Name);
Assert.AreEqual(sourceProduct.Price, product.Price);
I hope you agree that this scales a lot better than a predicate.
How it works
There's a small amount of magic being done by the Capture<T> class. Take a look at the source code. The amount of code needed for the actual capture is a one-liner, but there is an implicit conversion that enables the terse syntax and then some checks to prevent you from shooting yourself in the foot. There's also a Values property for multiple captures, and HasValues as a shortcut for Values.Count > 0.
Note that the implementation is not thread-safe. Unit tests should avoid dealing with threading if at all possible, but if you need the class to be thread-safe, it is easily added.
How it really really works
For those taking a close look at the implementation: You may be wondering what happens in this case:
var productArg = new Capture<Product>();
A.CallTo(() => repository.SetProductStock(productArg, 0));
A.CallTo(() => repository.SetProductStock(A<Product>._, 1));
repository.SetProductStock(new Product(), 1);
I fully expected the product argument to be captured, even though the constraint on the second parameter is not met. Surprisingly, I could not reproduce this potential problem. FakeItEasy appears to do some deep magic to check the constraints without executing the predicate I'm passing. I've even tried some more complex cases where all constrains were predicates, but all of them seemed to work as desired. Going through the FakeItEasy code I still don't fully understand, so if you can either give me an example which breaks, or explain why it doesn't, I would be most grateful :)
Files: