Moq - 使用在测试执行期间更改的参数验证调用

Fab*_*bio 5 c# unit-testing moq

鉴于以下代码

public class Entity
{
    public string Name { get; set; }
    public string Status { get; set; }
}

public interface IRepository
{
    void InsertEntity(Entity entity);
    void UpdateEntity(Entity entity);
}

public class Processor
{
    private IRepository _repository;

    public Processor(IRepository repository)
    {
        _repository = repository;
    }

    public void Execute(string name)
    {
        var entity = new Entity() { Name = name, Status = "Initialized" };
        _repository.InsertEntity(entity);
        // do other things with the entity
        entity.Status = "Processed";
        _repository.UpdateEntity(entity);
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以编写一个单元测试来验证是否在 Execute 方法中调用了存储库,并使用方法 InsertEntity 保存实体的值。换句话说,我想确保当调用 InsertEntity 时,实体的 Status 属性的值是“已初始化”。所以我的单元测试是这样的:

[TestMethod]
public void ShouldSaveEntityWithStatusInitialized()
{
    var mock = new Mock<IRepository>();
    var processor = new Processor(mock.Object);
    processor.Execute("test");
    mock.Verify(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")), Times.Once()); // fail
}
Run Code Online (Sandbox Code Playgroud)

但是,此代码甚至无法使用 Status = "Initialized" 调用 InsertEntity 方法(我已经调试过了)。我认为这是因为实体对象在 Execute 方法的执行过程中发生了更改(最后,Status 属性更改为“已处理”)并且 Moq 验证针对更改后的对象的调用。事实上,这个其他单元测试运行良好。

[TestMethod]
public void ShouldUpdateEntityWithStatusProcessedAtTheEnd()
{
    var mock = new Mock<IRepository>();
    var processor = new Processor(mock.Object);
    processor.Execute("test");
    mock.Verify(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Processed")), Times.Once());
}
Run Code Online (Sandbox Code Playgroud)

我发现使我的第一个单元测试有效的唯一方法是使用以下解决方法。我使用 Moq 的回调功能保存 Status 属性的值,并在稍后声明。

[TestMethod]
public void ShouldSaveEntityWithStatusInitialized_withWorkaround()
{
    var mock = new Mock<IRepository>();
    var processor = new Processor(mock.Object);
    string status = string.Empty;
    mock.Setup(m => m.InsertEntity(It.IsAny<Entity>())).Callback((Entity e) => status = e.Status);
    processor.Execute("test");
    Assert.AreEqual("Initialized", status);
}
Run Code Online (Sandbox Code Playgroud)

但我不喜欢那样。我想知道是否有办法让 Moq 验证在 STU(被测系统)执行期间对模拟对象的调用,而不是在所有执行完成之后。

谢谢

Gen*_*e S 3

恕我直言,最后一种方法(您称之为“黑客”)是测试状态值的正确方法。我更清楚的是,您要在此测试中验证的是在InsertEntity调用方法时状态设置为“已初始化”。

对于您正在测试的内容,您想要使用的验证方法更加模糊。您是想确认该InsertEntity参数被调用还是想测试该参数是否已“初始化”,或者两者兼而有之,我不知道。如果您想测试两者,那么这实际上是两个不同的单元测试。

您还可以执行以下操作...

mock.Setup(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")));
Run Code Online (Sandbox Code Playgroud)

但我也不喜欢这种方法,因为这意味着InsertEntity当状态不是“初始化”值时,将运行生产代码而不是您的模拟。更有可能的是,单元测试仍然会失败,但从单元测试返回的断言失败消息更加模糊(失败是由于实际InsertEntity方法内部发生的事情而发生的)。回调准确地说明了您在单元测试中测试的内容。