FakeItEasy 模拟对象修改方法

FakeItEasy to mock an object-modifying method

我正在尝试为依赖依赖项的方法编写单元测试,该依赖项提供接受对象并修改它的方法,但不 return 它在 "new path" 上,例如作为 return 值或按引用参数。

public class Product
{
    public string Name { get; set; }
}

public interface IFixer
{
    void Modify(Product product);
}

public class Fixer: IFixer
{
    public void Modify(Product product)
    {
        if (string.IsNullOrEmpty(product.Name))
        {
            product.Name = "Default";
        }
    }
}

public class Manager()
{
    private readonly IFixer _fixer;

    public Manager(IFixer fixer)
    {
        _fixer = fixer;
    }

    public bool IsProductNew(int id)
    {
         var product = GetProduct(id); // Gets an object instance from a repository, e.g. a file or a database, so we can have something to operate on. 

         _fixer.Modify(product);

         return product.Name != "Default";
    }
}

所以我希望能够测试我的 Manager class' IsProductNew() 方法:

var fakeFixer = A.Fake<IFixer>();

var manager = new Manager(fakeFixer);

var isNew = manager.IsProductNew(A<int>._);

Assert.True(isNew);

我在这里缺少的是:如何模拟 IFixer.Modify() 的行为,即让它修改 Product 对象实例?

So I want to be able to test my Manager class' IsProductNew() method

您无法以当前形式对其进行有效测试。您不会获得 100% 的覆盖率。您应该 :

直接传递产品:

public bool IsProductNew(Product product)
{
     _fixer.Modify(product);

     return product.Name != "Default";
}

您可以通过以下方式进行测试:

var fakeFixer = A.Fake<IFixer>();
var manager = new Manager(fakeFixer);
var product = new Product { Id = 100, Name = "Default" }; // create another test for where Name != "Default"
var isNew = manager.IsProductNew(product);

// Assert that fixer.modify is called with the product
A.CallTo(() => fakeFixer.Modify(A<product>.That.Matches(p => p.Id == 100))).ShouldHaveBeenCalledOnceExactly();

// Should return false because of product name = "Default"
Assert.IsFalse(isNew);

,传递一个"product getter"到Manager()构造函数中:

public Manager(IFixer fixer, IProductGetter productGetter)
{
    _fixer = fixer;
    _productGetter = productGetter;
}
...

public bool IsProductNew(int id)
{
     var product = _productGetter.GetProduct(id);

     _fixer.Modify(product);

     return product.Name != "Default";
}

您可以通过以下方式进行测试:

var fakeProductGetter = A.Fake<IProductGetter>();

// Prime the product getter to return a product
A.CallTo(() => fakeProductGetter.Get(A<int>.Ignored))
    .Returns(new Product{
        Id = 100,
        Name = "Default" // create another test for where Name != "Default"
    });

var fakeFixer = A.Fake<IFixer>();

var manager = new Manager(fakeFixer, fakeproductGetter);

var isNew = manager.IsProductNew(100);

// Assert that a call is made to productGetter.Get with the ID
A.CallTo(() => productGetter.Get(100)).MustHaveHapennedOnceExactly();

// Assert that fixer.modify is called with the product
A.CallTo(() => fakeFixer.Modify(A<product>.That.Matches(p => p.Id == 100))).ShouldHaveBeenCalledOnceExactly();

// Should return false because of product name = "Default"
Assert.IsFalse(isNew);

您所做的选择取决于您是希望 IsProductNew() 方法负责通过 ID 获取产品,还是希望直接传递产品。

有效回答这个问题取决于 GetProduct(id);

的定义

但是,如果 IFixer 实现没有影响或不良行为,那么就真的没有必要模拟它。

//Arrange
var fixer = new Fixer();    
var manager = new Manager(fixer);

//Act
var isNew = manager.IsProductNew(1);

//Assert
Assert.True(isNew);

但要回答

How do I mock the behaviour of IFixer.Modify(), i.e. have it modify a Product object instance?

您需要一个捕获匹配参数的回调

//Arrange
var fakeFixer = A.Fake<IFixer>();
A.CallTo(() => fakeFixer.Modify(A<Product>._))
    .Invokes((Product arg) => arg.Name = "Not Default Name");

var manager = new Manager(fakeFixer);

//Act
var isNew = manager.IsProductNew(1);

//Assert
Assert.True(isNew);

引用Invoking Custom Code