Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify method calls using with different state of object using Moq

Recently I discovered quite interesting behaviour of Moq library (4.5.21) in one of mine C# projects. Below is the class that I am trying to test.

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

Below is my 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);
}

I get following output:

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

I'd expect that I can Verify that the method was called Once each time with an object with different State. Any thoughts on what am I doing wrong?

Thank you!

like image 532
user6810960 Avatar asked Sep 08 '16 20:09

user6810960


Video Answer


2 Answers

Setup the mock to use a callback. Inside the callback count the number of times the method is called

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

Here is the full test method

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


}
like image 69
robor Avatar answered Oct 12 '22 20:10

robor


This looks very similar to this post

Basically it's because by the time the first verify is called, the reference that the mock holds (for the particular call) has had its State property changed to "open" (just before the second call). This can be proven by changing the Order class to a struct to make sure that the mock captures a copy of the object rather than a reference to the object itself.

EDIT: For the sake of a complete answer and the above post uses Expect which is now obsolete, here is a passing test:

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);
like image 38
Scott Perham Avatar answered Oct 12 '22 20:10

Scott Perham