FakeItEasy说MustHaveHappened并没有发生......但确实如此

Bob*_*bor 7 c# unit-testing mocking fakeiteasy

我正在尝试对"服务层"/"应用程序外观层"方法进行单元测试.这是我试图进行单元测试的方法:

// Create a new order in the database for a customer.  Given a customer id,
// will create a new order and return an OrderDto for use in the presentation
// layer.
public OrderDto CreateOrderForCustomer(int customerId)
{
  // Find the customer
  var customer = _customerRepository.GetCustomerById(customerId);

  // Create an order and apply special logic to get it ready for use.
  var orderFactory = new OrderFactory();
  var order = orderFactory.CreateOrder(customer);

  // IMPORTANT: This is what I'm trying to unit test ...
  _orderRepository.Save(order);

  order.Status = "Editing";

  // Using AutoMapper to turn this into a DTO that will be returned
  // to the Presentation layer.  The Mappings are created in the 
  // constructor and not depicted in this code snippet.
  var orderDto = Mapper.Map<Order, OrderDto>(order);

  return orderDto;
}
Run Code Online (Sandbox Code Playgroud)

(注意......为了清楚起见,我在这里添加了大量的注释.我通常不会这么说.)

由于这个方法的工作是编排域层方法和持久层方法来创建一个空订单,坚持下去,并将其作为一个简单的DTO返回,我认为这对FakeItEasy来说是一个很棒的工作......我只是确保那些关键方法正在精心编排,确保使用FakeItEasy的MustHaveHappened()调用它们.

所以,考虑到这一点,这是我创建的单元测试:

[TestMethod]
public void CreateOrderForCustomer_ValidCustomer_CreatesNewOrder()
{
  // Arrange
  var customer = _customerRepository.GetCustomerById(1);
  Assert.AreEqual(0, customer.Orders.Count);

  // Act
  var orderDto = _orderEntryService.CreateOrderForCustomer(1);

  // Assert

  // Here I'm trying to make sure to re-create the order that was actually 
  // sent into the _customerRepository.Save() ... I should be able to
  // simple un-map the OrderDto back to an Order, and undo the property 
  // change.
  var order = Mapper.Map<OrderDto, Order>(orderDto);
  order.Status = "New";

  A.CallTo(() => _customerRepository.GetCustomerById(1)).MustHaveHappened();

  // **THIS CAUSES AN EXCEPTION**
  A.CallTo(() => _orderRepository.Save(order)).MustHaveHappened();
  Assert.AreEqual(1, customer.Orders.Count);
}
Run Code Online (Sandbox Code Playgroud)

在单元测试中,我无法访问在测试方法中创建的ACTUAL Order,我尝试做下一个最好的事情...通过被测方法返回订单的DTO版本,map订单的DTO版本返回到域模型Order的新实例,并确保属性相同,然后再将其发送到FakeItEasy的MustHaveHappened().

我已经调试了单元测试并且看到了ACTUAL Order的属性而不是FAKED Order的属性......我向你保证,它们是相同的.此外,我可以通过调试确认_customerRepository.Save(order)确实被调用.

问题 .MustHaveHappened()是否失败,因为我实际上是在发送Order对象的两个不同实例 - 即使它们的属性相同?即使属性相同,FakeItEasy是否需要输入参数的相同实例才能确保方法调用已经发生?

此外,关于我应该如何测试这种事情的任何建议(即,编排/服务/"应用程序外观"/你想要调用它的层方法)?

k.m*_*k.m 18

是.MustHaveHappened()失败,因为我实际上是在发送Order对象的两个不同实例 - 即使它们的属性是相同的?

是.FakeItEasy将使用.Equals,除非您的类重写它,否则引用类型默认为引用相等.

(...)FakeItEasy是否需要输入参数的相同实例以确保方法调用已经发生?

不.您可以像这样进行自定义参数匹配:

A.CallTo(() => _orderRepository.Save(A<Order>.That.Matches(o =>
    o.Status == "New" &&
    o.Id == 10
))).MustHaveHappened();
Run Code Online (Sandbox Code Playgroud)

但是,这个问题揭示了您的代码存在问题.从你的样本中可以清楚地看出你是_customerRepository一个依赖注入.那很棒.你为什么不这样做OrderFactory呢?如果它是通过接口/基类依赖注入的,那么你可以轻松地模拟(假)它并且你当前的问题不会存在.

如果你可以改变你的代码,我建议注射工厂(遵循简单的指导方针 - "不,new这是个好消息!").如果没有,请使用自定义匹配器验证订单属性,就像我在上面的示例中所做的那样.