首页 文章

在c#中使用Moq进行模拟

提问于
浏览
21

我有以下代码:

public interface IProductDataAccess
{
    bool CreateProduct(Product newProduct);
}

ProductDataAccess 实现该接口 .

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

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

4 回答

  • 34

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

    这就是爱的任何模拟框架强制你做的事情 - 编写解耦代码 .

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

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

    尝试添加行为测试,您希望数据访问层抛出异常(使用“.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;
      }
    }
    

    并使用任何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
    
  • 21

    您应该将 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;
        }
    }
    

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

    var productDataAccess = new Mock<IProductDataAccess>();
    var productBusiness = new ProductBusiness(productDataAccess.Object);
    
  • 8

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

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

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

    var mockDataAccess = new Mock<IProductDataAccess>();
    mockDataAccess
        .Setup(m => m.CreateProduct(It.IsAny<Product>()))
        .Returns(true);
    
    var productBusiness = new ProductBusiness(mockDataAccess.Object);
    // ... test behaviour here
    

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

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

  • 6

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

    properties 注入:

    public class ProductBusiness
    {
        IProductDataAccess Pda {get; set;}
    }
    
    var productBusiness = new ProductBusiness();
    productBusiness.Pda = new ProductDataAccess();
    productBusiness.Pda = new MockProductDataAccess();
    

    或构造函数注入:

    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());
    

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

相关问题