Caster Troy
Caster Troy

Reputation: 2866

How can I isolate a data source such as DbSet?

I have a number of controllers that I am testing, each of which has a dependency on a repository. This is how I am supplying the mocked repository in the case of each test fixture:

[SetUp]
public void SetUp()
{
    var repository = RepositoryMockHelper.MockRepository();
    controller = new HomeController(repository.Object);
}

And here is the MockRepository helper method for good measure:

internal static Mock<IRepository> MockRepository()
{
    var repository = new Mock<IRepository>();

    var posts = new InMemoryDbSet<Post>()
    {
        new Post {
            ...
        },
        ...
    };

    repository.Setup(db => db.Posts).Returns(posts);

    return repository;
}
... = code removed for the sake of brevity. 

My intention is to use a new instance of InMemoryDbSet for each test. I thought that using the SetUp attribute would achieve this but clearly not.

When I run all of the tests, the results are inconsistent as the tests do not appear to be isolated. One test will for example, remove an element from from the data store and assert that the count has been decremented but according to the whim of the test runner, another test might have incremented the count, causing both tests to fail.

Am I approaching these tests in the correct way? How can I resolve this issue?

Upvotes: 4

Views: 191

Answers (3)

guillaume31
guillaume31

Reputation: 14072

As @PatrickQuirk pointed out, I think your problem is due to what InMemoryDbSet does under the covers.

Regarding the "Am I approaching this right ?" part :

If, as I suspect, your Repository exposes some kind of IDbSet, it's probably a leaky abstraction. The contract of an IDbSet is far too specific for what a typical Repository client wants to do with the data. Better to return an IEnumerable or some sort of read-only collection instead.

From what you describe, it seems that consumers of Posts will manipulate it as a read-write collection. This isn't a typical Repository implementation - you'd normally have separate methods such as Get(), Add(), etc. The actual internal data collection is never exposed, which means you can easily stub or mock out just the individual Repository operations you need and not fear the kind of issues you had with your test data.

Upvotes: 2

Patrick Quirk
Patrick Quirk

Reputation: 23757

The package you reference you are using for your InMemoryDataSet uses a static backing data structure, and so will persist across test runs. This is why you're seeing the inconsistent behaviors. You can get around this by using another package (as you mention), or pass in a new HashSet to the constructor in every test so it doesn't use a static member.

As to the rest of your question, I think you're approaching the testing well. The only thing is that since all of your tests have the same setup (in your SetUp method), they could influence each other. I.e., if you have a test that relies on having no Foo objects in the set and one that needs at least one, then you're either going to add one in SetUp and remove it in the test that doesn't need it, or vice versa. It could be more clear to have specific set up procedures as @BillSambrone mentioned.

Upvotes: 3

Bill Sambrone
Bill Sambrone

Reputation: 4464

Whatever is in your [SetUp] method will be called for each test. This is probably behavior that you don't want.

You can either place the code you have in the [SetUp] method inside each individual test, or you can create a separate private method within your unit test class that will spin up a freshly mocked DbSet for you to keep things DRY.

Upvotes: 1

Related Questions