Different return values for same method and arguments
If a method is called multiple times with the same arguments, how to let different calls return different values? For example, let's say I have a class:
public class Calendar
{
public int SumRandom()
{
var r = new Random();
return r.Next(1, 10) + r.Next(1, 10);
}
}
I want the 1st call to r.Next(1, 10) returns 7 and the 2nd call returns 8.
One of the way I found is like below:
[Test]
public void TestSumRandom()
{
var randomValue1 = 7;
var randomValue2 = 8;
var fake = new Fake<Calendar>();
var sut = fake.Rewrite(f => f.SumRandom());
int randomCount = 0;
sut.Replace((Random r) => r.Next(1, 10))
.When(() => {
if (randomCount == 0)
{
randomCount++;
return true;
}
return false;
})
.Return(randomValue1);
sut.Replace((Random r) => r.Next(1, 10))
.When(() => {
if (randomCount == 1)
{
randomCount++;
return true;
}
return false;
})
.Return(randomValue2);
Assert.That(sut.Execute(), Is.EqualTo(randomValue1 + randomValue2));
}
But it's a little tedious. Is there a simpler way to achieve it?
No simplier way. The library is designed to cover very corner cases that are not coverable by classic mocking libraries. So here the focus is to prefer universe to convinience. However, you can still refactor code to look more elegant, e.g.
[Test]
public void TestSumRandom()
{
var randomValue1 = 7;
var randomValue2 = 8;
var fake = new Fake<Calendar>();
var sut = fake.Rewrite(f => f.SumRandom());
int randomCount = 0;
sut.Replace((Random r) => r.Next(1, 10))
.When(() => randomCount++ == 0)
.Return(randomValue1);
sut.Replace((Random r) => r.Next(1, 10))
.When(() => randomCount++ == 1)
.Return(randomValue2);
Assert.That(sut.Execute(), Is.EqualTo(randomValue1 + randomValue2));
}
Looks elegant enough to me. Another way to make it better is to introduce some extensions methods on your project level. Then you get desired API and there is no need to pollute the lib's API. Although if you think that your use case is quite common and popular, you can put your suggestions and we can think about them together.
Thank you. That looks better to me. I was considering whether to propose this library to my team, so I wanted to see what this library could and could not do. I think this is one of the common use cases. Another use case is capturing the arguments used and then grabbing the argument values later for verification. It is suitable for complex object arguments where the matcher isn't convenient. However, you may want to keep this library simple and focus on specific cases, so it's up to you. Thank you again.
Could you please tell more on the second use case? Isn't it already supported?
var fake = new Fake<Sut>();
var list = new List<int> { 0 };
Console.WriteLine(string.Join(", ", list));
var sut = fake.Rewrite(() => Sut.MutateArgument(list));
sut.Execute();
Console.WriteLine(string.Join(", ", list));
public class Sut
{
public static void MutateArgument(List<int> list) => list.Add(1);
}
0
0, 1
https://dotnetfiddle.net/Ewhv4o
P.S.
I wanted to see what this library could and could not do
Yes, that's my big bad that there is no documentation. Hope to fix that this year...
Aah, guess you meant actual arguments used in replacements, etc. They actually used to be accessible by public API some time ago. Let me think of this once again and probably expose them back. If so, it will be object[][] under fake's instance or something like that.
Aah, guess you meant actual arguments used in replacements, etc. They actually used to be accessible by public API some time ago. Let me think of this once again and probably expose them back. If so, it will be
object[][]under fake's instance or something like that.
Yes. For clarity, it is something like below:
using AutoFake;
namespace Test
{
public class Order
{
public IList<LineItem> Items { get; }
public Order()
{
Items = new List<LineItem>();
}
}
public class LineItem
{
public string ProductName;
public int Price;
public LineItem(string productName, int price)
{
ProductName = productName;
Price = price;
}
}
public class OrderService
{
public string CreateOrder(Order order)
{
return "100";
}
}
public class RequestHandler
{
private OrderService _orderService = new OrderService();
public string HandleRequest()
{
Order order = new Order();
order.Items.Add(new LineItem("product 1", 100));
order.Items.Add(new LineItem("product 2", 200));
return _orderService.CreateOrder(order);
}
}
public class Tests
{
[Test]
public void TestHandleRequest()
{
var fake = new Fake<RequestHandler>();
var sut = fake.Rewrite(f => f.HandleRequest());
sut.Replace((OrderService orderService) => orderService.CreateOrder(Arg.IsAny<Order>()));
sut.Execute();
// Capture the recorded "order" arguments of the calls to "OrderService.CreateOrder"
List<Order> capturedOrders = sut.Captured(() => orderService.CreateOrder(Capture.IsAny<Order>()));
Assert.That(capturedOrders.Count, Is.EqualTo(1));
Assert.That(capturedOrders.First().Items.Count, Is.EqualTo(2));
Assert.That(capturedOrders.First().Items[0].ProductName, Is.EqualTo("product 1"));
}
[Test]
public void TestHandleRequest2()
{
var fake = new Fake<RequestHandler>();
var sut = fake.Rewrite(f => f.HandleRequest());
Func<Order, bool> checkOrder = (order) => {
Assert.That(order.Items.Count, Is.EqualTo(2));
Assert.That(order.Items[0].ProductName, Is.EqualTo("product 1"));
return true;
};
sut.Replace((OrderService orderService) => orderService.CreateOrder(Arg.Is(checkOrder))).Return("1000");
sut.Execute();
}
}
}
The method TestHandleRequest uses an non-existing method Captured to describe the argument we want to capture. Because the CreateOrder might have been called several times, the return value is a List<Order>. I referenced the way that mockito is designed.
But as you can see, it's also achievable with a matcher as shown in TestHandleRequest2.
Therefore, it's just for convenience, not functional limitation.
This is achievable and I'd say convenient enough because you can always write extensions but this might stop working if HandleRequest has two CreateOrder calls. I will think of a more simple/universe Captured analog.
Unfortunately, I still don't see something better than exposing object[][] Arguments property on sut which is worse than existing API because of types. So I tend to keep API as is if there are no other points supporting the request.
Unfortunately, I still don't see something better than exposing
object[][] Argumentsproperty onsutwhich is worse than existing API because of types. So I tend to keep API as is if there are no other points supporting the request.
I see, thanks. Please feel free to close this issue.