Search code examples
unit-testingmspec

Unit testing against repositories, with MSpec, am I doing this right?


A second MSpec question from me in a day, this is a new record. I'm trying to get smart on MSpec very quickly and I've run into some old problems I've always had with MSpec.

Scenario: I have a repository that contains a bunch of cartoons. Right now I only need to filter this set on a single Name parameter, which is a string. As I'm told I'll need to filter this on more properties later on, I decide to create a class which takes in my ICartoonRepository via IoC, and contains a simple method that's called GetByName(string name).

You might argue this is overkill, but I'm trying to teach myself how to use MSpec and work in a more TDD manner.

So I create the following:

[Subject(typeof(CartoonViewModelBuilder))]
public class when_cartoon_repository_is_asked_to_get_by_id : specification_for_cartoon_viewmodel_builder
{
    static string name;
    static Cartoon the_cartoon;
    static Cartoon result;

    Establish context = () =>
    {
        name = "Taz";
        the_cartoon = new Cartoon();
        the_cartoon_repository.Stub(r => r.GetAll().Where(x=>x.Name == name).FirstOrDefault()).Return(the_cartoon);
    };

    Because of = () => result = subject.GetByName(name);

    It should_return_cartoon = () => result.ShouldBeTheSameAs(the_cartoon);
}

This fails on the stub as the repository is empty. I have a couple other tests that pass fine (simply testing the GetAll(), etc). Do I need to add things to the repository to test it? This is where I'm stumped, please be gentle.

Also, if I'm writing the linq statement in the stub, it seems like I'm doing it twice, in the actual implementation and in the test. Is this the point? It doesn't feel right. Is there a better way I can write this test?

For clarity sake, here is the actual implementation (I'm omitting the interface and the class, which just has one property:

public class CartoonViewModelBuilder: ICartoonViewModelBuilder
{
    readonly ICartoonRepository _cartoonRepository;

    public CartoonQueryObject(ICartoonRepository  cartoonRepository)
    {
        _cartoonRepository = cartoonRepository;
    }

    public IList<Cartoon> GetAllCartoons()
    {
        return _cartoonRepository.GetAll();
    }

    public Cartoon GetByName(string name)
    {
        return _cartoonRepository.GetAll().Where(x => x.Name == name).FirstOrDefault();
    }
}

Edit 1: Based on the lack of responses, I should say that if I were using something like NUnit, I would be creating a method on the testing class that was like, "LoadDummyData" and threw data into the repository, then I'd do complex filtering or view model building and sort of manually checked what happened. This made large refactoring a chore. It seems like specs allows you to avoid that?

Edit 2: Here's my corrected test which now passes. Let me know if I'm doing it right, I think I am. Thanks again for the hand holding!

    static string name;
    static Cartoon the_cartoon;
    static Cartoon result;
    static IQueryable<Cartoon> the_cartoons;

    Establish context = () =>
    {
        name = "Taz";
        the_cartoon = new Cartoon {Name = name};
        the_cartoons = new List<Cartoon> {the_cartoon, new Cartoon(), new Cartoon() }.AsQueryable();
        the_cartoon_repository.Stub(r => r.GetAll()).Return(the_cartoons.ToList());
    };

    Because of = () => result = subject.GetByName(name);

    It should_return_cartoon = () => result.ShouldBeTheSameAs(the_cartoon);

Edit 3: Gave you both points, but I can only award one best answer unfortunately.


Solution

  • The actual reason of this test failing is the way you're mocking your repository. I would be very surprised if method chains like r.GetAll().Where(x=>x.Name == name).FirstOrDefault() could be mocked so easily, as it uses LINQ extension methods and lambda clauses. The framework should really throw NotSupported exception or something to let you know that you can't mock LINQ queries as a whole.

    To mock LINQ query result, you should provide properly prepared underlying data collection, which is the starting point of LINQ query. In your example you should mock just r.GetAll() to return a collection containing your element with proper name. The actual query will run on your "mocked" data and retrieve the object you expect.

    This removes the need to duplicate your LINQ query in code and in test, what is strange, as you noted.

    EDIT: Code in your edit is like I've suggested, technically OK.

    Anyway, by now it's a bit overkill, as you've said. Your class under test doesn't do anything beside the call to the mocked repository, so the value of that test is rather small. But it may be a good start if you're going to have some more logic in GetByName method.