Moq中Verify()和Setup()的区别是什么?VerifyAll()又是什么?

6

我正在创建几个单元测试,希望验证某个方法是否被调用,并且参数的属性符合我的期望。

因此,考虑到这个非常简单的系统:

public class Employee
{
    public bool IsEmployed { get; set; }
}

public class DataStore
{
    public void UpdateEmployee(Employee obj)
    {
        // Save in DB
    }
}

public interface IDataStore
{
    void UpdateEmployee(Employee employee);
}

public Employee FireEmployee(IDataStore dataStore, Employee employee)
{
    employee.IsEmployed = false;

    dataStore.UpdateEmployee(employee);

    return employee;
}

我希望验证当 Employee.IsEmployed 属性设置为 false 时,DataStore.UpdateEmployee() 方法是否被调用。因此,这里有两个测试用例,我认为它们应该实现相同的功能。

[Test]
public void TestViaVerify()
{
    //Arrange
    Mock<IDataStore> dataStore = new Mock<IDataStore>();
    var robert = new Employee { IsEmployed = true };

    //Act
    FireEmployee(dataStore.Object, robert);

    //Assert
    dataStore.Verify(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)), Times.Once);
}

[Test]
public void TestViaSetupVerifyAll()
{
    //Arrange
    Mock<IDataStore> dataStore = new Mock<IDataStore>();
    dataStore.Setup(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)));

    var robert = new Employee { IsEmployed = true };

    //Act
    FireEmployee(dataStore.Object, robert);

    //Assert
    dataStore.VerifyAll();
}

根据系统当前的代码,两个测试都如预期一样通过了。

现在假设另一个开发人员不小心把 Employee.IsEmployed = false; 的设置移到了 DataStore.UpdateEmployee() 方法之后。在这种情况下,我希望我的测试会失败,因为该雇员将不会被标记为失业状态。

public Employee FireEmployee(IDataStore dataStore, Employee employee)
{
    dataStore.UpdateEmployee(employee);

    employee.IsEmployed = false;

    return employee;
}

现在当我运行测试时:
TestViaVerify方法通过。
TestViaSetupVerifyAll方法失败。
我本来期望它们都会失败,但看起来在TestViaVerify()方法中,方法中的lambda表达式是在测试结束时执行的,此时Employee.IsEmployed已经被设置为false。
有没有一种只使用Verify方法就可以实现我的目标的方法?而不必使用Setup...VerifyAll方法?如果没有,我将采用TestViaVerifyAll()方法。

这似乎是moq的问题。因为您正在通过引用更改已捕获的对象。在调用dataStore.UpdateEmployee之前,您可以尝试复制一份副本。我的假设是Verify也会失败。 - Johnny
2个回答

1
老实说,自从我上次更新moq源代码已经超过2年了。无论是Verify还是VerifyAll都基于捕获到的虚拟实例的每个调用(包括参数)。 Verify将查找方法/属性调用并验证捕获的调用(及其捕获的参数),而VerifyAll将获取所有设置方法并执行与Verify方法相同的操作。
由于捕获的参数是ByRef参数,如果最后一段仍然相关,则可以在调用Verify/VerifyAll之前添加robert.IsEmployed = true;而导致UT失败:
[Test]
public void TestViaVerify()
{
    ....
    robert.IsEmployed = true; // will make this UT to failed
    //Assert
    dataStore.Verify(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)), Times.Once);
}

[Test]
public void TestViaSetupVerifyAll()
{
    ....
    robert.IsEmployed = true; // will make this UT to failed
    //Assert
    dataStore.VerifyAll();
}

我记得以前我回答过类似的问题(并提供了更多示例),我的解决方法是结合使用 SetupCallback,因为我不喜欢使用 VerifyAll 模式:

....
var invokedCorrectly = false;
dataStore.Setup(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)))
         .Callback<Employee>(x=> invokedCorrectly = true);

//Act
FireEmployee(dataStore.Object, robert);

//Assert
Assert.IsTrue(invokedCorrectly);

1
这是moq的预期行为,因为调用捕获的参数是通过身份识别进行比较的,使用Equals而不是值。一旦您更改了捕获的参数,就会直接更改调用。然后稍后验证这些对象不再相同。正如@Old Fox已经提供了一个解决方案,我只想再添加一个。您可以使用Verify()而不是VerifyAll(),区别在于前者仅检查标记为Verifiable()的设置。在您的情况下,可以这样做:
[Test]
public void TestViaSetupVerifyAll()
{
    //Arrange
    Mock<IDataStore> dataStore = new Mock<IDataStore>();
    dataStore
        .Setup(x => x.UpdateEmployee(It.Is<Employee>(e => e.IsEmployed == false)))
        .Verifiable();

    var robert = new Employee { IsEmployed = true };

    //Act
    FireEmployee(dataStore.Object, robert);

    //Assert
    dataStore.Verify();
}

如果您将设置标记为Verifiable(),则可以捕获具有实际预期对象状态的特定调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接