2016-09-08 3 views
6

Недавно я обнаружил довольно интересное поведение библиотеки Moq (4.5.21) в одном из моих проектов на C#. Ниже приведен класс, который я пытаюсь проверить.Проверка вызовов методов с использованием другого состояния объекта с использованием Moq

public class Order 
{ 
    public string State { get; set; } 
} 

public interface IOrderService 
{ 
    Task UpdateOrderAsync(Order order); 
} 

public class Program 
{ 
    public async Task RunAsync(IOrderService orderService) 
    { 
     var order = new Order(); 

     order.State = "new"; 
     await orderService.UpdateOrderAsync(order); 

     order.State = "open"; 
     await orderService.UpdateOrderAsync(order); 
    } 
} 

Ниже мой TestClass:

[TestMethod] 
public async Task TestMethod() 
{ 
    var mock = new Mock<IOrderService>(); 
    await new Program().RunAsync(mock.Object); 

    mock.Verify(x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "new")), Times.Once); 
    mock.Verify(x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "open")), Times.Once); 
} 

Я получаю следующий вывод:

Moq.MockException: 
Expected invocation on the mock once, but was 0 times: x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "new")) 


Configured setups: 
x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "new")), Times.Once 
x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "open")), Times.Once 

Performed invocations: 
IOrderService.UpdateOrderAsync(Order<State:open>) 
IOrderService.UpdateOrderAsync(Order<State:open>) 

Я бы ожидать, что я могу Verify, что метод был назван Once каждый раз с object с разными State. Любые мысли о том, что я делаю неправильно?

Спасибо!

ответ

2

Это выглядит очень похоже на this post

В основном это потому, что к тому времени, первым верифицировать называются, ссылка, что макет выполняется (для конкретного вызова) была его собственность государства изменена на «открытый» (только перед вторым вызовом). Это можно доказать, изменив класс Order на структуру, чтобы убедиться, что макет захватывает копию объекта, а не ссылку на сам объект.

EDIT: Ради полного ответа и пост выше использует Ожидайте, который в настоящее время устарели, вот прохождение теста:

var mock = new Mock<IOrderService>(MockBehavior.Loose); 

var newCalled = false; 
var openCalledBeforeNew = false; 

mock.Setup(x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "new"))).Callback(() => newCalled = !openCalledBeforeNew).Returns(Task.FromResult((object)null)); 
mock.Setup(x => x.UpdateOrderAsync(It.Is<Order>(o => o.State == "open"))).Callback(() => openCalledBeforeNew = newCalled).Returns(Task.FromResult((object)null)); 

await new Program().RunAsync(mock.Object); 

Assert.IsTrue(newCalled); 
Assert.IsTrue(openCalledBeforeNew); 
2

Setup макет использовать функцию обратного вызова. Внутри обратного вызова подсчитывают число раз метод называется

mock.Setup(a => a.UpdateOrderAsync(It.Is<Order>(o => o.State == "new"))) 
    .Returns(Task.Factory.StartNew(() => { })) 
    .Callback(() => countOrderStateNew++); 

Вот полный тестовый метод

[TestMethod] 
public async Task TestMethod1() 
{ 

    var mock = new Mock<IOrderService>(); 

    Order o1 = new Order() { State = "new" }; 

    int countOrderStateNew = 0; 
    int countOrderStateOpen = 0; 

    mock.Setup(a => a.UpdateOrderAsync(It.Is<Order>(o => o.State == "new"))) 
     .Returns(Task.Factory.StartNew(() => { })) 
     .Callback(() => countOrderStateNew++); 
    mock.Setup(a => a.UpdateOrderAsync(It.Is<Order>(o => o.State == "open"))) 
     .Returns(Task.Factory.StartNew(() => { })) 
     .Callback(()=> countOrderStateOpen++); 

    await new Program().RunAsync(mock.Object); 

    Assert.AreEqual(1, countOrderStateNew); 
    Assert.AreEqual(1, countOrderStateOpen); 


}