I have a Key-value store keyValueDatabase
. To request data IKeyValueResult keyValueDatabase.GetKeyValue(string id, out IKeyValue value)
has to be called with the id for the requested value. The value is given back through the out parameter as an object derived from IKeyValue. The interfaces looks like this:
public interface IKeyValue
{
string ID { get; set; }
}
//analogue IKeyValueString...
public interface IKeyValueDouble : IKeyValue
{
double Value { get; set; }
}
Now I configure a stub of this key-value store using the code below.
ReturnedKeyValues
is a collection of stubs of different types of IKeyValue
s I created.
IKeyValue keyValue;
keyValueDatabase.GetKeyValue(Arg.Any<string>(),
out Arg.Any<IKeyValue>()).ReturnsForAnyArgs(info =>
{
if (ReturnedKeyValues.Select(keyVal => keyVal.ID).Contains(info[0]))
{
info[1] = ReturnedKeyValues.First(keyVal => keyVal.ID == (string)info[0]);
return okResult;
}
else
{
info[1] = null;
return unavailableResult;
}
});
When using this stub the first time keyValueDatabase.GetKeyValue
with an id of let's say 'a' it gives back an out value of type IKeyValueDouble as it should. Now when calling this method a second time with the id 'b' a value of type IKeyValueString should be given back. However an ArgumentSetWithIncompatibleValueException is thrown in this case:
Could not set value of type ObjectProxy_1 to argument 1 (IKeyValue&) because the types are incompatible.'
Using Returns
instead of ReturnsForAnyArgs
behaves in the same way. I am using NSubstitute 4.2.0 with .Net-Framework 4.7.
Edit:
In my test I create my own databaste stub using an interface specified through an external library. I had to implement this database in production code.
public interface IDatabase
{
/// <summary>Returns the value of one entry</summary>
/// <param name="id">The entry's ID</param>
/// <param name="value">Returns the value of the entry</param>
/// <returns>The result of the read operation</returns>
IKeyValueResult GetKeyValue(string id, out IKeyValue value);
}
In ReturnedKeyValues is my list of stubbed IKeyValue
s stored, which should be given back:
private static List<IKeyValue> ReturnedKeyValues = new List<IKeyValue>()
{
createKeyValueA(), createKeyValueB()
};
private static IKeyValue createKeyValueA()
{
var keyVal = Substitute.For<IKeyValueDouble>();
keyVal.ID.Returns("a");
keyVal.Value.Returns(21.31);
return keyVal;
}
private static IKeyValue createKeyValueB()
{
var keyVal = Substitute.For<IKeyValueString>();
keyVal.ID.Returns("b");
keyVal.Value.Returns("GA7713");
return keyVal;
}
Further investigation revealed that the problem is related with reusing the out variable. After assigning outVal the first time it is of type IKeyValueDouble. When it is reused it should be assigned to the type IKeyValueString. However the aforementioned exception is thrown. This happens both when called successionally:
IKeyValue outVal;
keyValueDatabase.GetKeyValue("a", out outVal);
keyValueDatabase.GetKeyValue("b", out outVal);
or when the variable is reused through compiler optimization in a loop:
foreach (string key in keys)
{
...
IKeyValue outVal;
IKeyValueResult success = keyValueDatabase.GetKeyValue(key.ID, out val);
...
}
As a workaround I set the value of the out variable to null before setting it to the actual value:
IKeyValue keyValue;
keyValueDatabase.GetKeyValue(Arg.Any<string>(),
out Arg.Any<IKeyValue>()).ReturnsForAnyArgs(info =>
{
if (ReturnedKeyValues.Select(keyVal => keyVal.ID).Contains(info[0]))
{
//Workarround: Setting the out variable to null before assigning a
//new value fixes the problem
info[1] = null;
info[1] = ReturnedKeyValues.First(keyVal => keyVal.ID == (string)info[0]);
return okResult;
}
else
{
info[1] = null;
return unavailableResult;
}
});
Bug in NSubstitute 3.1.0. Fixed with version 4.2.1