【问题标题】:mocked method won't return object模拟方法不会返回对象
【发布时间】:2013-07-09 11:54:44
【问题描述】:

我使用 .net mvc3 构建简单的应用程序。我写了几个测试用例,其中一个有问题。我使用 Moq 来模拟我的域服务,我有这两种方法:

List<Customer> customersRepo =
{
  new Customer{
   Id = 0,
   Name = "Jojo"
  },
  new Customer{
   Id = 1,
   Name = "John"
  }
}

mock.Setup(m => m.GetByName(IsAny<string>())).Returns<string>(
 n =>
 customersRepo.Where(c => c.Name == n)
);

mock.Setup(m => m.GetById(IsAny<int>())).Returns<int>(
  n=>
  customersRepo.Where(c => c.Id == n)
);

问题是,当我使用模拟对象的方法按名称获取客户时,我得到了我要求的那个,但是当我尝试按 id 获取时,我总是从模拟对象中得到空对象。我都尝试了id 0 和 1.. 可能是什么问题?

谢谢

【问题讨论】:

  • 我无法相信这些设置中的任何一个都能按预期工作。两种设置都返回一个IEnumerable&lt;Customer&gt;,但一个说它返回一个string,另一个说它返回一个int。两者都不是IEnumerable&lt;Customer&gt;

标签: .net unit-testing moq


【解决方案1】:

我认为您应该返回第一个或默认客户:

mock.Setup(m => m.GetById(IsAny<int>())).Returns<int>(
  id =>
  customersRepo.FirstOrDefault(c => c.Id == id)
);

另外请记住,您不需要在模拟中重新实现存储库逻辑(这很奇怪而且非常脆弱)。它是模拟的。您可以在没有任何逻辑的情况下模拟您想要的任何结果:

mock.Setup(m => m.GetById(42)).Returns<int>(new Customer { Id = 42 });

使用模拟来验证交互 - 即您的存储库的客户端使用预期参数调用预期方法。


如果你想测试某个服务的业务逻辑,那么服务就是一个被测系统(SUT),你不应该模拟它。但是如果你的服务既有业务逻辑又有数据访问逻辑,那么它做的事情就太多了。将数据访问逻辑提取到某个存储库类,该类将实现接口:

public interface ICustomerRepository
{
    Customer GetById(int id);
    // other methods related to customr data access
}

然后让你的服务依赖这个接口(依赖倒置):

public class YourService
{
   private readonly ICustomerRepository _repository;
   // dependency injection
   public YourService(ICustomerRepository repository)
   {
       _repository = repository;
   }

   public void ExecuteSomeBusinessLogic()
   {
       // your code will go here
   }
}

然后为服务编写测试。因此服务需要依赖项(客户存储库),您应该模拟此依赖项。并验证服务是否如您所愿与依赖项交互。例如。在您的 ExecuteSomeBusinessLogic 测试中,我们应该检查服务是否会要求 id 等于 42 的客户(是的,奇怪的业务逻辑):

[Test]
public void ShouldPerformGoodStuffWhenCustomerFound()
{
    // Arrange
    var mockRepository = new Mock<ICustomerRepository>();
    mockRepository.Setup(r => r.GetById(42)).Returns(new Customer { Id = 42 });
    var service = new YourService(mockRepository.Object);
    // Act
    service.ExecuteSomeBusinessLogic();
    // Assert
    mockRepository.VerifyAll();
    // check other stuff
}

如果您要在数据库中找不到自定义的情况下编写测试,只需设置不同的返回:

mockRepository.Setup(r => r.GetById(42)).Returns(null);

【讨论】:

  • 我已经在 linq 的末尾尝试了 FirstOrDefault(),这也不是我的存储库逻辑。我只是将数据存储模拟为一个列表,并且其中几乎没有记录。然后我模拟我的客户服务以进行单元测试。但是,我将更改完成方式,我将添加新用户(为测试做准备),然后尝试使用该方法获取它)
  • mock.Setup(m => m.GetCustomerById(It.IsAny())).Returns( id => customersRepo.FirstOrDefault(c => c.Id == id));
  • 你是什么意思 Returns,我在互联网上读到,返回中的 Generic 类型实际上是我想在模拟方法中接收的参数类型,所以我可以根据该参数采取行动,即 int 是我在模拟对象上调用 GetById 方法时发送的实际 id ..
  • @stefo0O0o 抱歉,我有点错过语法。 GetCustomerById 方法的签名是什么?
  • 我没有做太多的单元测试和模拟(仍在学习)但是如果我模拟整个服务,那么进行单元测试有什么意义......我不应该只模拟存储库和使用我已经编码(或将要实现)的服务来进行测试,以确保我的服务健壮且安全?
【解决方案2】:

我实际上只是改变了一点我的基础设施..现在我的服务在构造函数中获取了 DataSource 接口对象..这是独立的数据源,它依赖于它来获取客户对象,ofc。在它检查业务规则并对用户输入进行任何需要的清理之后。所以现在,我将在单元测试中模拟我的数据源(包含客户对象的列表),并使用这个模拟的 repo 提供我的服务实例客户对象..这样我可以测试我已经编码的服务业务逻辑的正确性。以前我已经将服务与特定的数据存储(即使用 EF DbContext 的 db)耦合,并且在单元测试中使用该服务有点困难,因为它已经与 db 耦合,但我不想改变和恢复数据库状态只是为了执行我的单元测试,所以我想用简单的客户对象列表来模拟数据存储。谢谢你的努力,帮了我:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-03
    • 2023-01-03
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多