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

时间:2012-12-19 20:35:58

标签: 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);
    }
}

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

[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
}

但是,即使调用Status =“Initialized”的InsertEntity方法,此代码也会失败(我已调试过)。我认为这是因为实体对象在Execute方法执行期间被更改(最后Status属性更改为“Processed”)并且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());
}

我发现进行第一次单元测试的唯一方法是使用以下解决方法。我使用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);
}

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

由于

2 个答案:

答案 0 :(得分:3)

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

您要与Verify一起使用的方法更准确地说明您正在测试的内容。你想确认调用了InsertEntity,或者你想测试参数是“已初始化”,还是两者都有,我不知道。如果你想要测试两者,那么实际上这将是两个不同的单元测试。

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

mock.Setup(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")));

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

答案 1 :(得分:1)

[TestMethod]
public void ShouldSaveEntityWithStatusInitialized()
{
    // arrange
    var mock = new Mock<IRepository>();
    mock.Setup(m => m.InsertEntity(It.Is<Entity>(e => e.Status == "Initialized")));
    var processor = new Processor(mock.Object);

    // act
    processor.Execute("test");

    // assert
    mock.Verify();
}