【问题标题】:Using MOQ to test a repository使用 MOQ 测试存储库
【发布时间】:2013-11-03 21:43:38
【问题描述】:

我正在尝试使用 MOQ 来测试存储库以模拟存储库的行为。我是 MOQ 的新手,所以请多多包涵。

给定以下方法:

public static SubmissionVersion DeleteNote(IRepository repository, SubmissionVersion version, Guid noteId)
{
    Note note = repository.GetById<Note>(noteId);
    version.Notes.Remove(note);
    repository.Save(version);
    repository.Delete(note);
    return repository.GetById<SubmissionVersion>(version.Id);
}

这个测试看起来好吗?

[Fact]
public void DeleteNoteV2()
{
    // Arrange
    var note = new Note{ Id = Guid.NewGuid()};

    var subVersion = new Mock<SubmissionVersion>();
    subVersion.Setup(x => x.Notes.Remove(note));

    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(note.Id)).Returns(note);
    repo.Setup(x => x.GetById<SubmissionVersion>(It.IsAny<Guid?>())).Returns(subVersion.Object);

    // Act
    SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);

    // Assert
    repo.Verify(x => x.GetById<Note>(note.Id), Times.Once());
    repo.Verify(x => x.Save(subVersion.Object), Times.Once());
    repo.Verify(x => x.Delete(note), Times.Once());

    subVersion.Verify(x => x.Notes.Remove(It.IsAny<Note>()), Times.Once());
}

【问题讨论】:

  • 测试它是否有效的一个好方法是注释掉你的代码并确认测试在预期的点失败,然后再次包含代码并确保它通过......这就是 TDD如此好的方法,因为它也可以作为您正在编写的测试的验证。

标签: c# unit-testing moq xunit xunit.net


【解决方案1】:

你的方法很好,但是我会调整一些东西。我对您的测试和模拟组件进行了一些更改,您可以在下面看到。

单元测试

您在单元测试中测试的方法(见下文)与您在问题中定义的方法并不真正匹配。

单元测试方法调用:

SubmissionVersion.DeleteNote(repo.Object, subVersion.Object, note.Id.Value);

待测方法:

public static SubmissionVersion DeleteNote
  (IRepository repository, SubmissionVersion version, Guid noteId)

我假设上述方法是另一个类的一部分,我将其称为 NotesHelper - 这不是存储库调用的理想名称,但它只是为了让编译工作。

我会亲自将您的单元测试分成 2 个单独的单元测试。一是验证是否调用了所需的方法,二是验证笔记是否已从集合中删除。

另外,我没有创建一个模拟的 SubmissionVersion,而是创建了一个 fakeSubmissionVersion。 这是因为 SubmissionVersion 中的例程可能不可模拟。您还可以进行基于状态的测试(首选)以确保 version.Notes.Remove(note);已被调用。

[Fact]
public void DeleteNote_Deletion_VerifyExpectedMethodsInvokecOnlyOnce()
{
    // Arrange
    var note = new Note { Id = Guid.NewGuid() };
    var fakeSubmissionVersion = new SubmissionVersion() { Notes = new List<Note>() };
    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(It.IsAny<Guid>())).Returns(note);

    // Act
    NotesHelper.DeleteNote(repo.Object, fakeSubmissionVersion, note.Id.Value);

    // Assert
    repo.Verify(x => x.GetById<Note>(note.Id), Times.Once());
    repo.Verify(x => x.Save(fakeSubmissionVersion), Times.Once());
    repo.Verify(x => x.Delete(note), Times.Once());
}

可以通过在自己的测试中定义每个验证来进一步改进上述测试。

[Fact]
public void DeleteNote_Deletion_VerifyDeleteMethodCalledOnlyOnce()

这样,如果一个测试失败,我们将准确地知道哪个方法没有被期望调用。有时,如上所述进行多次验证可能会出现问题。例如,如果尚未调用 Save 方法,您将永远不会知道 Delete 方法是否已被调用。这是因为在Save方法没有被调用并且测试执行已经终止的时候抛出了异常。

这将导致多次测试,但它们更具可读性和可维护性。当然你可以将通用代码重构为工厂或辅助方法。

[Fact]
public void DeleteNote_RemoveNotes_ReturnsExpectedNotes()
{
    // Arrange
    var note = new Note { Id = Guid.NewGuid() };
    var fakeSubmissionVersion = new SubmissionVersion() { Notes = new List<Note>() { note } };
    var repo = new Mock<IRepository>();
    repo.Setup(x => x.GetById<Note>(It.IsAny<Guid>())).Returns(note);

    // Act
    NotesHelper.DeleteNote(repo.Object, fakeSubmissionVersion, note.Id.Value);

    // Assert
    Assert.AreEqual(0, fakeSubmissionVersion.Notes.Count);
}

附加说明:还提供描述性单元测试方法名称可提高单元测试的可读性。

【讨论】:

  • 您能否再为我解释一下:You method under test within your Unit Test (see below), does not really match with the method which you have defined in your question.
  • @Sam,没问题。那是关于您的问题“鉴于以下方法”: public static SubmissionVersion DeleteNote(IRepository repository, SubmissionVersion version, Guid noteId) - 我相信这是您测试的方法(被测方法)。但不清楚您的测试目标是什么类(被测系统(SUT))。您还有 ... new Mock() 所以我无法推断出您的 SUT 是什么,即使您的测试意图很明确。所以我介绍了 NotesHelper(不理想)作为你的 SUT。
猜你喜欢
  • 1970-01-01
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-11
  • 1970-01-01
  • 2014-12-04
相关资源
最近更新 更多