"Delayed execution". What a term!! A very good explanation with examples can be found here, but I'll try a small definition myself: the execution of a piece of code (a delegate or lambda expression) or the enumeration of a collection will happen not when and where you define the piece of code, but when the code or the enumeration is actually used or enumerated.
It brings a lot of goodness: allowing Linq queries composition before execution, dealing with infinite collections, closures,...
But it also can bring some of surprises. For instance, the scenario which I encountered: unit testing, record/play mode using a mock library and filtering enumerables using Linq.
We all do love unit testing, don't we? Most of all love mock objects and mock libraries facilities, right? Some of us still like a lot the record/replay/verify paradigm that others dislike so much. For me it is not annoying to move along the line of interaction testing, although I have to give to its opponents that is it somewhat harder to grasp than simple state testing. If we like mocks, we must love mock libraries that save us from creating all those ugly and boring stubs and spies.
So I was testing a piece of code that given some data in the form of collection of objects, performs some filtering using some logic encapsulated as a specification on them and calls a collaborator with just the filtered elements.
The purpose of my test was being sure that the code performed the filtering correctly. And how did it test it? Well, checking that the collaborator was called the right number of times with the expected arguments (and those expected arguments where verified using constraints).
Let's see how our system under test (SUT) looks like:
Simple filtering and the selection calls the collaborator, creating a new object with the results of the query.
Now let's have a look at the supporting types: ICollaborator, IOperationSubject, ISubjectElement, and OperationResult:
ICollaborator only received a collection of ISubjectElement and a string, returning a dto: TransformedElement. The rest of the classes are mere data holders that get information in the constructor and return it via a property.
As I said before, the way to test some behavior of the Sut is provide it with an entry in which we control the ISubjectElement in regards of their behavior with the predicate and set an recurring expectation on the injected collaborator object. The code for this phrase:
Using the comments is very easy to follow this test:
- Record: set the expectation on the collaborator object, so that the first argument complies with a constraint that just executed the predicate and the second is whatever we set on the IOperationSubject argument
- Replay: call the PerformOperation() method on the SUT
- Verify: check that we have as many results in the output as we expect. This is done "twice" as the mocking framework also check the expectation exactly twice. Note that we verify outside of the playback region.
And that last phrase is the key. I we ran this unit test, we would obtain a failing test. The symptom of the failing test is the mocking framework complaining because it expected a call on the collaborator twice, but none was issued.
What? is that our method does not filter correctly? Yes it does. Well, if would do, if we asked it to do it. It happens that the filtering (and thus, the calls to the collaborator) does not happen when we call the PerformOperation() method in the replay region. It only happens when we ask the TransformedElements collection to evaluate, and that happens only when Count() is invoked (outside the replay region and therefore after the framework checked the expectations). The code above would work perfectly without a change if we didn't use enumerations, but lists, when returning the result from the filtering, as doing so would cause the enumerable to enumerate before leaving the PerformOperation() method.
The explanation makes it obvious, but it took me some time to realize what was going on (late-in-the-day coding and exhausted neurons don't help).
In order to check that everything works as it should, as indeed does, we only need to include the assertion inside the replay region. And everything should work then.
I hope it saves some time someone or at least make her/him think what they are doing and what new challenges we face when testing code that uses new techniques.
Modified: I realized a bit to late that there is no need for creating the SubjectElementConstraint, as using the Is.Match() constraint creator with the predicate object would suffice.