tags:

views:

390

answers:

3

After reading another SO thread (http://stackoverflow.com/questions/396513/should-parameters-returns-of-collections-be-ienumerablet-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).

Cheers, Berryl

+2  A: 

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()).

tvanfosson
Hi. Good news, bad news - the facade is injected also. I'm looking for a way to keep the benefits of passing the IEnumerable but be able to test it as a parameter, if I can. I tried putting ToList() in the unit test call, but that just changes the error to a more understandable parameter mismatch. Where did you mean for the ToList() to go? Thx
Berryl
I suspect that someone down the chain there is a "new" operation that is generating a different element. Rather than spend a lot of time on it, you might consider leaving the no argument check and making sure that the correct elements are available in the enumeration. Isn't that really the definition of correct here -- that the right elements are loaded?
tvanfosson
I don't see how I can check the elements were loaded in a test of the interface. I only have access to the IEnumerable as an argument to the method call, as far as I can see. How would you do that?
Berryl
Credits for the move on advice; I even took your advice to validate the elements in the Enumerable after leaving the ignore arguments option in BUT IMO the real definition of correct (ie, 'useful', and therefore correct) is to have a test that fails if the parameter is wrong in the production code. Oh well.. move on!! Thx
Berryl
+1  A: 

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
    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.

David B
Hi David. Deferred execution does sound cooler than late execution - is that the more proper description? What about the _assembler.ToActivityCollection() tells you that probably isn't the case? I'm looking for a way to keep the benefits of passing the IEnumerable but be able to test it as a paramter, if I can. Cheers -
Berryl
ToActivityCollection either enumerates its input and creates a new collection, or it casts the input to the new collection. Since you don't control the actual implementation type of the Cast method, it's unlikely that casting to your collection will work. So I conclude that enumeration must occur. (Unless the ActivityDtoCollection is just given the Enumerable and resolves it in a deferred manner, but I suspect you would know that if it were the case).
David B
ToActivityCollection is given an IEnumerable<ActivityBase> and enumerates each one, using yield return ToDto(activity) to produce an IEnumerable<Dto>. I'm guessing from what I've learned from you and what I see in the debugger that this is deferred execution.
Berryl
+1  A: 

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..

Berryl