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!
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);
}
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With