在c#中使用Moq进行模拟

Ana*_*i M 22 c# unit-testing moq mocking

我有以下代码:

public interface IProductDataAccess
{
    bool CreateProduct(Product newProduct);
}
Run Code Online (Sandbox Code Playgroud)

类ProductDataAccess实现了该接口.

public class ProductBusiness
{
    public bool CreateProduct(Product newProduct)
    {
        IProductDataAccess pda = new ProductDataAccess();
        bool result = pda.CreateProduct(newProduct);
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如何CreateProduct通过模拟IProductDataAccess接口来创建方法的单元测试?我想到了一个IProductDataAccess内部的公共实例ProductBusiness并使用Mock<IProductDataAccess>object 初始化它,但是将数据访问暴露给UI层并不是一个好习惯.谁能帮我?

Amo*_*mol 37

经典示例,显示如何无法对特定组件进行单元测试,REFACTOR组件!

这就是爱的任何模拟框架强制执行的任务 - 编写解耦代码.

在您的示例中,ProductBusiness该类与该类紧密耦合ProductDataAccess.您可以使用(像大多数答案所示)依赖注入来解耦它.通过这样做,你最终将取决于IProductDataAccess抽象,而不是任何具体的实现.

另外需要注意的是,当您编写业务层的测试/规范时,通常需要测试"行为"而不是"状态".因此,虽然您可以使用断言验证是否返回"true",但您的测试应该真正测试是否使用MOQ我们实际执行的MOQ设置的预期数据访问调用是否使用MOQ的".Verify"API.

尝试添加行为测试,您希望数据访问层抛出异常(使用".Throws"API)并检查是否需要在业务层进行任何特殊处理.

像Kevin建议的那样,让ProductBusiness看起来像:

public class ProductBusiness
{
  private readonly IProductDataAccess  _productDataAccess;

  public ProductBusiness(IProductDataAccess productDataAccess)
  {
      _productDataAccess = productDataAccess;
  }

  public bool CreateProduct(Product newProduct)
  {
    bool result=_productDataAccess.CreateProduct(newProduct);
    return result;
  }
}
Run Code Online (Sandbox Code Playgroud)

并使用任何xunit测试框架将测试编写为:

 var mockDataAccess = new Mock<IProductDataAccess>();
 mockDataAccess.Setup(m => m.CreateProduct(It.IsAny<Product>())).Returns(true);
 var productBusiness = new ProductBusiness(mockDataAccess.Object);
 //behavior to be tested
Run Code Online (Sandbox Code Playgroud)


Ufu*_*arı 22

您应该将IProductDataAccess接口注入为依赖项:

public class ProductBusiness
{
    private IProductDataAccess _productDataAccess;    

    public ProductBusiness(IProductDataAccess productDataAccess)
    {
        _productDataAccess = productDataAccess;
    }

    public bool CreateProduct(Product newProduct)
    {
        bool result = _productDataAccess.CreateProduct(newProduct);
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在测试中用mock替换它:

var productDataAccess = new Mock<IProductDataAccess>();
var productBusiness = new ProductBusiness(productDataAccess.Object);
Run Code Online (Sandbox Code Playgroud)


Kev*_*tch 9

通过您当前设计ProductBusiness类的方式,无法IProductDataAccess使用模拟更改实现.推荐的模式是,您可以通过构造函数获取类型的依赖关系.所以你的班级成为:

public class ProductBusiness
{
  private readonly IProductDataAccess  _productDataAccess;

  public ProductBusiness(IProductDataAccess productDataAccess)
  {
      _productDataAccess = productDataAccess;
  }

  public bool CreateProduct(Product newProduct)
  {
      bool result = _productDataAccess.CreateProduct(newProduct);
      return result;
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用像这样的框架来测试您的类.例如:

var mockDataAccess = new Mock<IProductDataAccess>();
mockDataAccess
    .Setup(m => m.CreateProduct(It.IsAny<Product>()))
    .Returns(true);

var productBusiness = new ProductBusiness(mockDataAccess.Object);
// ... test behaviour here
Run Code Online (Sandbox Code Playgroud)

现在,您可以更改模拟在设置步骤中的行为方式,并确保您的CreateProduct方法行为正确.

我还会看一下像这样的依赖注入框架.依赖注入框架可以自动解决依赖关系,这意味着创建新类型更容易,因为您不必手动新建所有内容.此外,它意味着您可以更改在一个位置使用的实现,并在任何地方进行更改.


dca*_*tro 6

您不应该ProductDataAccess在CreateProduct方法中实例化具体内容.相反,IProductDataAccess应该是一个注射依赖.这可以通过以下两种方式之一完成:

物业注入:

public class ProductBusiness
{
    IProductDataAccess Pda {get; set;}
}

var productBusiness = new ProductBusiness();
productBusiness.Pda = new ProductDataAccess();
productBusiness.Pda = new MockProductDataAccess();
Run Code Online (Sandbox Code Playgroud)

或构造函数注入:

public class ProductBusiness
{
    private readonly IProductDataAccess _pda;

    public ProductBusiness(IProductDataAccess pda)
    {

        _pda = pda;
    }
}

var productBusiness = new ProductBusiness(new ProductDataAccess());
var productBusiness = new ProductBusiness(new MockProductDataAccess());
Run Code Online (Sandbox Code Playgroud)

构造函数注入通常是推荐的方法.属性注入用于可选的依赖项(例如,NullLogger默认情况下在构造函数中实例化具体,并使用该属性可选地注入工作的记录器).