Reputation: 12833
After reading another SO thread (Should parameters/returns of collections be IEnumerable<T> or T[]?), I am of the opinion that a unit testing 'issue' I'm having is probably related to what the thread refers to as "late evaluation".
I am passing three different but related flavors IEnumerables from a presenter class to my UI. The best news is that it works, but I can't seem to find a way to make a unit test that definitively proves that the right IEnumerable is being passed at the right time.
I have a facade with the following method:
public IEnumerable<DynamicDisplayDto> NonProjectDtos
{
get
{
var activities = LeaveTimeActivities.Cast<TimeSheetActivityBase>()
.Concat(AdministrativeActivities.Cast<TimeSheetActivityBase>());
return _assembler.ToActivityDtoCollection(activities, _timeSheet);
}
}
In a presenter, I load a widget:
private ITimeSheetMatrixWidget _nonProjectActivityMatrix;
public ITimeSheetMatrixWidget NonProjectActivityMatrix {
set {
..
// load the activities
_nonProjectActivityMatrix.Load(Facade.NonProjectDtos);
...
}
}
Then the test on a mocked matrix (using Rhino Mocks):
[Test]
public void SettingTheWidget_TriggersLoad_NonProjectActivities() {
var f = _getFacade();
...
// inject the mocked widget & trigger the Load
var widget = MockRepository.GenerateMock<ITimeSheetMatrixWidget>();
timeSheetPresenter.ActivityMatrix = widget;
widget.AssertWasCalled(x => x.Load(f.NonProjectDtos),
mo =>mo.IgnoreArguments()); <-- only way not to fail
//widget.AssertWasCalled(x => x.Load(f.NonProjectDtos)); <-- error
}
If I look in the debugger, I can see that the IEnumerable load arg is evaluating to Domain.TransferObjects.TimeSheetDtoAssembler +d__1, which is also the part of Rhino's message that the method call failed.
Is this is related to late evaluation? Is there a reasonably elegant way to test this more rigorously?
I would also like to fully understand the intermediate evaluation better, which looks a lot like the method that assembled it (in the facade code above).
Upvotes: 1
Views: 3031
Reputation: 110101
I just want to address your late evaluation concern:
Both Cast
and Concat
(and many System.Linq.Enumerable
methods) return instances that comply with IEnumerable<T>
. The actual results of enumerating these instances are deferred. Why are these results deferred and not eagerly determined?
List<int> firstThreePrimes = Enumerable.Range(0, 1000000)
.Where(i => isPrime(i))
.Take(3)
.ToList();
ToList
enumerates the IEnumerable<int>
result from Take(3)
. No numbers are generated by Range
, filtered by Where
or limited by Take
until ToList
enumerates this result.
If the results from Range were eagerly determined, we would generate 1000000 ints.
If the results from Where
were eagerly determined, then the runtime would have to send all of those 1000000 ints to our isPrime
method.
Instead, the call sequence looks like this.
Range
return from Range
Where
return from Where
Take
return from Take
ToList
GetEnumerator(Take)
GetEnumerator(Where)
GetEnumerator(Range)
return 0
isPrime(0) is false
return 1
isPrime(1) is false
return 2
isPrime(2) is true
2 is the first result
List.Add(2)
return 3
isPrime(3) is true
3 is the second result
List.Add(3)
return 4
isPrime(4) is false
return 5
isPrime(5) is true
5 is the third result
List.Add(5)
break;
This is easy to confirm with a debugger. Just step through and watch the calls to isPrime
.
From examining your code, it looks like _assembler.ToActivityDtoCollection
probably enumerates the result and you probably aren't experiencing deferred execution.
Upvotes: 2
Reputation: 12833
Rhino mocks works perfectly, although it isn't always able to know why you've used the wrong syntax or constraints :-)
The way to check an IEnumerable argument for equality is just use the following inline constraint:
Arg<T>.List.Equal(yourList)
Here's a full example:
[Test]
public void NonProjectMatrix_Injection_IsLoaded()
{
_nonProjectMatrix = MockRepository.GenerateMock<ITimeSheetMatrixWidget>();
var dtos = _facade.NonProjectDtos;
nonProjectMatrix.Expect(x => x.Load(Arg<IEnumerable<DynamicDisplayDto>>.List.Equal(dtos))).Return(dtos.Count());
new MatrixEntryService(_facade, _projectMatrix, _nonProjectMatrix, _totalMatrix);
_nonProjectMatrix.VerifyAllExpectations();
}
So the issue really had nothing to do with deferred execution. Rhino was just spitting out all it knew about a call not being made the way I told it to expect it, and that's how the IEnumerable looked at the time of the expectation failure.
Cheers..
Upvotes: 2
Reputation: 532435
Are the Facade objects both the same in the test and in the object under test -- i.e., are you injecting the Facade object as well? If the objects are different, this would cause the problem that you are seeing. If they are the same, then you could either realize the enumeration in the method (use ToList()).
Upvotes: 3