如何隔离数据源,如DbSet?

时间:2014-07-07 18:43:07

标签: c# unit-testing mocking tdd nunit

我正在测试一些控制器,每个控制器都依赖于存储库。这就是我在每个测试夹具的情况下提供模拟存储库的方式:

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

以下是适用于衡量标准的MockRepository辅助方法:

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. 

我的目的是为每个测试使用InMemoryDbSet的新实例。我认为使用SetUp属性可以实现这一点,但显然不是。

当我运行所有测试时,结果不一致,因为测试似乎没有被隔离。例如,一个测试将从数据存储中删除一个元素并断言计数已经减少但是根据测试运行器的异想天开,另一个测试可能会增加计数,导致两个测试都失败。

我是否以正确的方式接近这些测试?我该如何解决这个问题?

3 个答案:

答案 0 :(得分:3)

您引用的用于InMemoryDataSet的{​​{1}}结构的软件包使用static backing data结构,因此将在测试运行期间保持不变。这就是您看到不一致行为的原因。您可以使用另一个包(如您所述)来解决这个问题,或者在每个测试中将HashSet传递给the constructor,这样就不会使用静态成员。

关于你的其余问题,我认为你正在接近测试。唯一的问题是,由于您的所有测试都具有相同的设置(在SetUp方法中),因此它们可能会相互影响。即,如果您的测试依赖于集合中没有Foo个对象且需要至少一个对象,那么您要么在SetUp中添加一个并将其删除不需要它的测试,反之亦然。具体的设置程序可以更清楚,如@BillSambrone所述。

答案 1 :(得分:2)

正如@PatrickQuirk指出的那样,我认为你的问题是由于InMemoryDbSet所做的事情所致。

关于“我接近这个权利吗?”部分:

如果我怀疑你的Repository暴露了某种IDbSet,它可能是一个漏洞的抽象。 IDbSet的合同对于典型的Repository客户端想要对数据做什么来说太具体了。最好还是返回IEnumerable或某种只读集合。

根据您的描述,似乎Posts的消费者会将其作为读写集合进行操作。这不是典型的Repository实现 - 您通常有单独的方法,例如Get()Add()等。实际的内部数据集合永远不会公开,这意味着您可以轻松地存根或模拟只需要您需要的各个存储库操作,而不必担心您对测试数据的问题。

答案 2 :(得分:1)

每次测试都会调用[SetUp]方法中的任何内容。这可能是您不想要的行为。

你可以将你在[SetUp]方法中拥有的代码放在每个单独的测试中,或者你可以在你的单元测试类中创建一个单独的私有方法,它将启动一个新模拟的DbSet,让你保持干燥。