Reputation: 54801
This code is based on this answer here: https://stackoverflow.com/a/3135677/360211
public interface IService
{
void DoSomething(out string a);
}
[Test]
public void Test()
{
var mock = new Mock<IService>();
string expectedValue = "value";
mock.Setup(s => s.DoSomething(out expectedValue));
string actualValue;
mock.Object.DoSomething(out actualValue);
Assert.AreEqual("value", actualValue);
}
The assignment string expectedValue = "value";
is not required as far as I can see and as far as resharper is concerned. But remove it and the Assert fails!
So how is Moq reading the value from an out
parameter?
Upvotes: 2
Views: 140
Reputation: 27619
I think the reason is that what it is passing is just an expression of type Expression<Action<IService>>
and the arguments in the expression are captured (so if you changed the value of expectedValue the value in the expression changes too). Of note is that the expression is identical if you make it with a normal parameter instead of an out parameter.
I think probably it is just not doing anything to that parameter at all. When you setup the method you put a bunch of metadata in the Mock object telling it what you've done and when you call the method on your mock object it uses that metadata to work out what the return is or what else it might need to do.
Of note is that it probably isn't calling anything and passing it your parameter with the out modifier so the result is basically the same as if you didn't have that line, that is Mock just doesn't even try to set a value of that variable. This means that basically you set the value of expectedValue
when you declare it and then nothing changes it and then you have an assertion based on it.
I'm not in a position to do any testing to prove this theory and I am not confident enough in my understanding of expressions or Mock to say this is definitely what is going on but from what I've seen of the code I think its at least close to this. My uncertainty was such that I was going to post this as a comment but it is a wee bit too long. ;-)
Having said all of that even if Mock is doing something with it the reason it can read your variable despite being an out parameter is that it is part of the expression. You should be able to see this by using the following code, setting an appropriate breakpoint and inspecting:
string expectedValue = "FNORDFNORD";
Expression<Action<IService>> expression = s => s.DoSomething(out expectedValue);
Upvotes: 0
Reputation: 1119
The important line is this:
mock.Setup(s => s.DoSomething(out expectedValue));
This setup means that any method that invokes DoSomething
will always put whatever the value is for expectedValue
into the actual out variable when the method executes.
Perhaps consider a more useful scenario that isn't directly testing the mock.Object
, but is testing a class that has an IService injected.
public class Foo
{
private IService service;
public Foo(IService service)
{
this.service = service;
}
public string GetData()
{
string outData;
service.DoSomething(out outData);
return outData != "" ? outData : "There was no data";
}
}
[Test]
public void FooTest_ServiceReturnsEmptyString()
{
var mock = new Mock<IService>();
// ReSharper disable once RedundantAssignment
string expectedValue = "";
mock.Setup(s => s.DoSomething(out expectedValue));
var fooObj = new Foo(mock.Object);
var result = fooObj.GetData();
Assert.AreEqual("There was no data", result);
}
[Test]
public void FooTest_ServiceReturnsValue()
{
var mock = new Mock<IService>();
string expectedValue = "data";
mock.Setup(s => s.DoSomething(out expectedValue));
var fooObj = new Foo(mock.Object);
var result = fooObj.GetData();
Assert.AreEqual(expectedValue, result);
}
In the two different tests, you can change what the mock returns, and you are now able to test how your class behaves given different data from your service.
Upvotes: 1
Reputation: 6604
An out
parameter is not required to be initialized, however it must be assigned to by the time the function returns.
I don't know what is going on inside mock.Object.DoSomething(out actualValue);
however, it seems weird that it is defined using an out
parameter. Judging by the behavior you are reporting, I would have though it should be a ref
instead. A ref
parameter must be initialized before being passed into the function as a parameter.
Upvotes: 0