Berryl
Berryl

Reputation: 12833

IEnumerable<T> evaluation and unit testing

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

Answers (3)

Amy B
Amy B

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

Berryl
Berryl

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

tvanfosson
tvanfosson

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

Related Questions