验证在单元测试中调用了一种方法或另一种方法

发布于 2024-07-26 16:24:30 字数 370 浏览 7 评论 0原文

示例:

public bool Save(MyObj instance)
{
    if (instance.IsNew)
    {
        this.repository.Create(instance);
    }
    else
    {
        this.repository.Update(instance);
    }
}

如何在 Moq 中创建一个测试来验证:

  1. 是否正在读取
  2. Create()属性 IsNew Update() 已被调用

Example:

public bool Save(MyObj instance)
{
    if (instance.IsNew)
    {
        this.repository.Create(instance);
    }
    else
    {
        this.repository.Update(instance);
    }
}

How do I create a test in Moq that verifies:

  1. that a property IsNew is being read
  2. that either Create() or Update() has been invoked

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

末蓝 2024-08-02 16:24:30

我的头顶上浮现出:
验证是否正在读取 IsNew 属性:

var mock = new Mock<MyObj>();
mock.Setup(m => m.IsNew).Returns(true).Verifiable();
//...
sut.Save(mock.Object);
//...
mock.Verify();

在上面的示例中,IsNew 属性将返回 true,因此将采用 Create 路径。

要验证是否调用了 Create 或 Update 方法,您需要对该功能进行一些挂钩。 看起来 Repository 是一个静态类,在这种情况下你不能用测试替身替换它,但我可能会以错误的方式读取你的代码......如果你可以用测试替身(模拟)替换它,您可以使用与上述相同的原则。

如果您可以在调用 Save 方法后检查存储库的状态,则可以通过基于状态的测试来判断遵循了哪两个代码路径。

如果两个代码路径的结果之间没有外部可观察的差异,那么最好不要测试这个特定的实现细节。 它可能会引导您走向一种称为 Overspecified Test 的反模式 - 您可以在优秀的书中阅读有关此反模式以及许多其他与单元测试相关的内容的更多信息 xUnit 测试模式


编辑:
测试存储库可以用相同的方式完成:

var myObjMock = new Mock<MyObj>();
myObjMock.Setup(m => m.IsNew).Returns(true);

var repositoryMock = new Mock<Repository>();
repositoryMock.Setup(m => m.Create(myObjMock.Object)).Verifiable();

var sut = new SomeClass(repositoryMock.Object);
sut.Save(myObjMock.Object);

repositoryMock.Verify();

对 Verulated 的调用是关键。 如果没有它,Moq 的默认行为就是让路,尽其所能,并且尽可能不抛出任何异常。

当您调用“可验证”时,您指示模拟期待该特定行为。 如果调用Verify时未满足该期望,它将抛出异常,从而导致测试失败。

Off the top of my head:
Verifying that the IsNew property is being read:

var mock = new Mock<MyObj>();
mock.Setup(m => m.IsNew).Returns(true).Verifiable();
//...
sut.Save(mock.Object);
//...
mock.Verify();

In the example above, the IsNew property will return true, so the Create path will be taken.

To verify that either the Create or Update method was invoked, you need to have some hook into that functionality. It looks like Repository is a static class, in which case you can't replace it with a Test Double, but I may be reading your code in the wrong way... If you can replace it with a Test Double (Mock), you can use the same principle as outlined above.

If you can examine the state of your Repository after the Save method has been called, you may be able to tell by State-Based Testing which of the two code paths were followed.

If there's no externally observable difference between the result of the two code paths, it's probably a better idea not to test this specific implementation detail. It might lead you towards an anti-pattern called Overspecified Test - you can read more about this anti-pattern and many other unit test-related things in the excellent book xUnit Test Patterns.


Edit:
Testing the repositories can be done in the same way:

var myObjMock = new Mock<MyObj>();
myObjMock.Setup(m => m.IsNew).Returns(true);

var repositoryMock = new Mock<Repository>();
repositoryMock.Setup(m => m.Create(myObjMock.Object)).Verifiable();

var sut = new SomeClass(repositoryMock.Object);
sut.Save(myObjMock.Object);

repositoryMock.Verify();

The call to Verifiable is the key. Without it, the default behavior of Moq is to get out of the way, do the best it can and not throw any exceptions if at all possible.

When you call Verifiable, you instruct the mock to expect that particular behavior. If that expectation has not been met when you call Verify, it will throw an exception, and thus fail the test.

一曲爱恨情仇 2024-08-02 16:24:30

不幸的是我自己有一个解决方案。

您所要做的就是将一个本地 int 变量设置为 0,然后模拟递增它。 最后你必须检查它的名称是否大于 0(或者恰好是 1,具体取决于问题)。

// Arrange
int count = 0;
Mock<Repository> mock = new Mock<Repository>();
mock.Setup<bool>(m => m.Create(It.IsAny<MyObj>())).Callback(() => count++);
mock.Setup<bool>(m => m.Update(It.IsAny<MyObj>())).Callback(() => count++);
// Act
...
// Assert
Assert.AreEqual(count, 1);

会有两次测试。 一种将属性 IsNew 设置为 true,另一种将其设置为 false

Unfortunately I have a solution myself.

All you have to do is to have a local int variable that you set to 0 and then mocking increments it. In the end you have to check whether its name is more than 0 (or exactly 1, depending on the problem).

// Arrange
int count = 0;
Mock<Repository> mock = new Mock<Repository>();
mock.Setup<bool>(m => m.Create(It.IsAny<MyObj>())).Callback(() => count++);
mock.Setup<bool>(m => m.Update(It.IsAny<MyObj>())).Callback(() => count++);
// Act
...
// Assert
Assert.AreEqual(count, 1);

There would be two tests. One that sets property IsNew to true and one that sets it to false.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文