Thursday, February 3, 2011

RhinoMocks: How do you properly mock an IEnumerable<T>?

I just keep stumbling through mocking...

The latest disaster was not grokking that I need to actually push results inside a mock object of IEnumerable...

Here's a sample (demonstration only of IEnumerable, not actually good Interaction-based testing!):

using System;
using System.Collections.Generic;
using Rhino.Mocks;
using MbUnit.Framework;

[TestFixture]
public class ZooTest{

    [Test]
    public void ZooCagesAnimals()
    {
        MockRepository mockery = new MockRepository();

        IZoo zoo = new Zoo();

        //this is the part that feels wrong to create
        IList<IAnimal> mockResults = mockery.DynamicMock<IList<IAnimal>>();
        IAnimal mockLion = mockery.DynamicMock<IAnimal>();
        IAnimal mockRhino = mockery.DynamicMock<IAnimal>();

        using (mockery.Record())
        {
            Expect.Call(zoo.Animals)
                .Return(mockResults)
                .Repeat.Once();
        }

        using (mockery.Playback())
        {
            zoo.CageThe(mockLion);
            zoo.CageThe(mockRhino);

            Assert.AreEqual(mockResults, new List<IAnimal>(zoo.Animals));
        }


    }
}


public class Zoo : IZoo
{
    private IList<IAnimal> animals = new List<IAnimal>();

    public void CageThe(IAnimal animal)
    {
        animals.Add(animal);
    }

    public IEnumerable<IAnimal> Animals
    {
        get
        {
            foreach(IAnimal animal in animals)
            {
                yield return animal;
            }
        }
    }
}

public interface IAnimal
{
}

public interface IZoo
{
    IEnumerable<IAnimal> Animals { get;}
    void CageThe(IAnimal animal);
}

I don't like how I got it to work for the following reasons:

  • Had to consume the IEnumerable results into IList, I understand this puts the results for checking onto the heap
  • Had to setup the contents of the results ... I understand this as well, but my main point is to test that Zoo.Animals is returning IEnumerable, and even better, that we're using "yield return" inside

Any suggestions on doing this better, or simpler?

Edit: I'm trying to determine the optimal way to test the interaction between IEnumerable and whatever I'm using. I'm not trying to test that Zoo can hold animals, rather that Zoo exposese as IEnumerable, and that yield return is getting used as well.

  • If you're testing the implementation, why are you trying to mock it out in the first place? Why not just CageThe(IAnimal) and then check that Animals contains that IAnimal?

    I get that you're mocking the IAnimals, seeing as apparently you don't yet have any concrete animals to play with, but why not just make them stubs, because obviously you're not expecting anything else to happen to them apart from being put into the list?

    Edit: Something roughly along these lines (not tested, might not compile, may eat your dog etc.):

    [TestFixture]
    public class ZooTest
    {
    [Test]
    public void ZooCagesAnimals()
    {
    MockRepository mockery = new MockRepository();

    IAnimal mockLion = mockery.Stub<IAnimal>();
    IAnimal mockRhino = mockery.Stub<IAnimal>();

    IZoo zoo = new Zoo();

    zoo.CageThe(mockLion);
    zoo.CageThe(mockRhino);

    List<IAnimal> animals = new List<IAnimal>(zoo.Animals);
    Assert.IsTrue(animals.Contains(mockLion));
    Assert.IsTrue(animals.Contains(mockRhino));
    }
    }
    From Rytmis
  • MockLions and MockRhinos won't do the trick here. You should really be using a MockTurtle.






    Sorry, had to do it. I'll take the reputation hit :P

    From Blorgbeard
  • @Rytmis: Thanks for the response, I've edited my question to make it clearer for what I'm asking. :)

    I want to construct a test such that the IEnumerable is tested and validated.

  • I don't quite see how you expect to set up mock expectations on an object that isn't a mock to begin with. Also, you're setting up the expectation to return an IList which isn't really what happens when the compiler generates an iterator.

    If you want to specifically test the iterator, you should probably

    Assert.IsNotNull(zoo.Animals);

    And then verify that the enumerator actually enumerates over all the things you added to the Zoo. Which is what I was going for there. :)

    I'm not sure if it's even possible to test if yield return is being called, because yield return is just syntactic sugar for a compiler-generated IEnumerable. For instance, calling

    zoo.Animals.GetEnumerator();

    will not execute any of the code you wrote in the enumerator. The first time that happens is on the first call to IEnumerator.MoveNext();

    Now, if you're trying to test the interaction between the concrete Zoo and the IEnumerable contained by that Zoo, you should make the IEnumerable a field on Zoo and inject a mock IEnumerable to that field instead of implementing a concrete IEnumerable directly in Zoo.

    I hope this is of some assistance.

    From Rytmis

0 comments:

Post a Comment