Implementing basic detection of type mismatches for Returns().

This commit is contained in:
David Tchepak 2013-04-26 14:16:13 +10:00
Родитель b29d08bb55
Коммит 0ce69f3af2
11 изменённых файлов: 156 добавлений и 40 удалений

Просмотреть файл

@ -1,4 +1,5 @@
using NSubstitute.Exceptions;
using System;
using NSubstitute.Exceptions;
using NUnit.Framework;
namespace NSubstitute.Acceptance.Specs.FieldReports
@ -7,19 +8,22 @@ namespace NSubstitute.Acceptance.Specs.FieldReports
{
public interface IFoo { string SomeString { get; set; } }
public interface IBar { IFoo GetFoo(); }
public interface IZap { int Num { get; set; } }
[Test]
[Pending, Explicit]
public void ShouldDetectTypeMismatchInReturns()
{
var sub = Substitute.For<IBar>();
Assert.Throws<CouldNotSetReturnException>(() =>
// GetFoo() called, then IPityTheFoo(), then Returns(..) is called.
// This means Returns(..) tries to update the last called sub,
// which is IFoo.SomeString, not IBar.GetFoo().
sub.GetFoo().Returns(IPityTheFoo())
);
var ex =
Assert.Throws<CouldNotSetReturnDueToTypeMismatchException>(() =>
// GetFoo() called, then IPityTheFoo(), then Returns(..) is called.
// This means Returns(..) tries to update the last called sub,
// which is IFoo.SomeString, not IBar.GetFoo().
sub.GetFoo().Returns(IPityTheFoo())
);
Assert.That(ex.Message, Is.StringStarting("Can not return value of type "));
}
private IFoo IPityTheFoo()
@ -28,5 +32,42 @@ namespace NSubstitute.Acceptance.Specs.FieldReports
foo.SomeString = "call a property so static LastCalled is updated";
return foo;
}
[Test]
public void ShouldDetectedWhenNestedReturnsClearsLastCallRouter()
{
var sub = Substitute.For<IBar>();
Assert.Throws<CouldNotSetReturnDueToNoLastCallException>(() =>
sub.GetFoo().Returns(CreateFooAndCallReturns())
);
}
private IFoo CreateFooAndCallReturns()
{
var foo = Substitute.For<IFoo>();
foo.SomeString.Returns("nested set");
return foo;
}
[Test]
public void ShouldDetectTypeMismatchWhenNullIsInvolved()
{
var sub = Substitute.For<IBar>();
var ex =
Assert.Throws<CouldNotSetReturnDueToTypeMismatchException>(() =>
sub.GetFoo().Returns(DoEvilAndReturnNullRef())
);
Assert.That(ex.Message, Is.StringStarting("Can not return null for"));
}
private IFoo DoEvilAndReturnNullRef()
{
var zap = Substitute.For<IZap>();
zap.Num = 2;
return null;
}
}
}

Просмотреть файл

@ -16,7 +16,7 @@ namespace NSubstitute.Acceptance.Specs.FieldReports
[Test]
public void Test_1_affected_by_test_0()
{
Assert.Throws<CouldNotSetReturnException>(() => 2.Returns(2));
Assert.Throws<CouldNotSetReturnDueToNoLastCallException>(() => 2.Returns(2));
}
public interface IFoo { }

Просмотреть файл

@ -116,20 +116,16 @@ namespace NSubstitute.Acceptance.Specs
[Test]
public void Throw_when_blatantly_misusing_returns()
{
const string expectedMessage =
"Could not find a call to return from.\n"+
"Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)).\n" +
"If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member.\n" +
"Return values cannot be configured for non-virtual/non-abstract members.";
const string expectedMessagePrefix = "Could not find a call to return from.";
var exception = Assert.Throws<CouldNotSetReturnException>(() =>
var exception = Assert.Throws<CouldNotSetReturnDueToNoLastCallException>(() =>
{
//Start with legitimate call to Returns (so the static context will not have any residual calls stored).
_something.Echo(1).Returns("one");
//Now we'll misuse Returns.
"".Returns("I shouldn't be calling returns like this!");
});
Assert.That(exception.Message, Contains.Substring(expectedMessage));
Assert.That(exception.Message, Is.StringContaining(expectedMessagePrefix));
}
[SetUp]

Просмотреть файл

@ -1,5 +1,6 @@
using System;
using NSubstitute.Core;
using NSubstitute.Exceptions;
using NSubstitute.Specs.Infrastructure;
using NUnit.Framework;
@ -12,7 +13,7 @@ namespace NSubstitute.Specs
protected ICallActions _callActions;
protected ICallResults _configuredResults;
protected IGetCallSpec _getCallSpec;
protected IReturn _returnValue;
protected IReturn _compatibleReturnValue;
public override void Context()
{
@ -20,7 +21,8 @@ namespace NSubstitute.Specs
_callActions = mock<ICallActions>();
_getCallSpec = mock<IGetCallSpec>();
_returnValue = mock<IReturn>();
_compatibleReturnValue = mock<IReturn>();
_compatibleReturnValue.stub(x => x.CanBeAssignedTo(It.IsAny<Type>())).Return(true);
}
public override ConfigureCall CreateSubjectUnderTest()
@ -37,9 +39,9 @@ namespace NSubstitute.Specs
var lastCallSpec = mock<ICallSpecification>();
_getCallSpec.stub(x => x.FromLastCall(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
sut.SetResultForLastCall(_returnValue, MatchArgs.AsSpecifiedInCall);
sut.SetResultForLastCall(_compatibleReturnValue, MatchArgs.AsSpecifiedInCall);
_configuredResults.received(x => x.SetResult(lastCallSpec, _returnValue));
_configuredResults.received(x => x.SetResult(lastCallSpec, _compatibleReturnValue));
}
}
@ -52,9 +54,9 @@ namespace NSubstitute.Specs
var callSpec = mock<ICallSpecification>();
_getCallSpec.stub(x => x.FromCall(call, MatchArgs.AsSpecifiedInCall)).Return(callSpec);
sut.SetResultForCall(call, _returnValue, MatchArgs.AsSpecifiedInCall);
sut.SetResultForCall(call, _compatibleReturnValue, MatchArgs.AsSpecifiedInCall);
_configuredResults.received(x => x.SetResult(callSpec, _returnValue));
_configuredResults.received(x => x.SetResult(callSpec, _compatibleReturnValue));
}
}
@ -68,7 +70,7 @@ namespace NSubstitute.Specs
var lastCallSpec = mock<ICallSpecification>();
_getCallSpec.stub(x => x.FromLastCall(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
var config = sut.SetResultForLastCall(_returnValue, MatchArgs.AsSpecifiedInCall);
var config = sut.SetResultForLastCall(_compatibleReturnValue, MatchArgs.AsSpecifiedInCall);
config
.AndDoes(firstAction)
.AndDoes(secondAction);
@ -77,5 +79,27 @@ namespace NSubstitute.Specs
_callActions.received(x => x.Add(lastCallSpec, secondAction));
}
}
public class When_setting_incompatible_result_for_call : Concern
{
[Test]
public void Configure_result_for_last_specified_call()
{
var lastCallSpec = mock<ICallSpecification>();
_getCallSpec.stub(x => x.FromLastCall(MatchArgs.AsSpecifiedInCall)).Return(lastCallSpec);
lastCallSpec.stub(x => x.GetMethodInfo()).Return(ReflectionHelper.GetMethod(() => SomeType.SampleMethod()));
var incompatibleReturn = mock<IReturn>();
incompatibleReturn.stub(x => x.CanBeAssignedTo(It.IsAny<Type>())).Return(false);
incompatibleReturn.stub(x => x.TypeOrNull()).Return(typeof (SomeType));
Assert.Throws<CouldNotSetReturnDueToTypeMismatchException>(
() => sut.SetResultForLastCall(incompatibleReturn, MatchArgs.AsSpecifiedInCall)
);
_configuredResults.did_not_receive(x => x.SetResult(lastCallSpec, incompatibleReturn));
}
}
private class SomeType { public static SomeType SampleMethod() { return null; } }
}
}

Просмотреть файл

@ -38,7 +38,7 @@ namespace NSubstitute.Specs
[Test]
public void Should_throw_if_trying_to_set_another_return_value_before_another_call_is_made_on_a_substitute()
{
Assert.Throws<CouldNotSetReturnException>(() => sut.LastCallShouldReturn(mock<IReturn>(), MatchArgs.AsSpecifiedInCall));
Assert.Throws<CouldNotSetReturnDueToNoLastCallException>(() => sut.LastCallShouldReturn(mock<IReturn>(), MatchArgs.AsSpecifiedInCall));
}
public override void Because()
@ -60,7 +60,7 @@ namespace NSubstitute.Specs
[Test]
public void Should_throw()
{
Assert.Throws<CouldNotSetReturnException>(() => sut.LastCallShouldReturn(mock<IReturn>(), MatchArgs.AsSpecifiedInCall));
Assert.Throws<CouldNotSetReturnDueToNoLastCallException>(() => sut.LastCallShouldReturn(mock<IReturn>(), MatchArgs.AsSpecifiedInCall));
}
}

Просмотреть файл

@ -20,10 +20,9 @@ namespace NSubstitute.Core
_argumentSpecifications = argumentSpecifications.ToArray();
}
public MethodInfo GetMethodInfo()
{
return _methodInfo;
}
public MethodInfo GetMethodInfo() { return _methodInfo; }
public Type ReturnType() { return _methodInfo.ReturnType; }
public bool IsSatisfiedBy(ICall call)
{

Просмотреть файл

@ -1,4 +1,4 @@
using System;
using NSubstitute.Exceptions;
namespace NSubstitute.Core
{
@ -18,14 +18,25 @@ namespace NSubstitute.Core
public ConfiguredCall SetResultForLastCall(IReturn valueToReturn, MatchArgs matchArgs)
{
var spec = _getCallSpec.FromLastCall(matchArgs);
CheckResultIsCompatibleWithCall(valueToReturn, spec);
_configuredResults.SetResult(spec, valueToReturn);
return new ConfiguredCall(action => _callActions.Add(spec, action));
}
public void SetResultForCall(ICall call, IReturn valueToReturn, MatchArgs matchArgs)
{
var callSpecification = _getCallSpec.FromCall(call, matchArgs);
_configuredResults.SetResult(callSpecification, valueToReturn);
var spec = _getCallSpec.FromCall(call, matchArgs);
CheckResultIsCompatibleWithCall(valueToReturn, spec);
_configuredResults.SetResult(spec, valueToReturn);
}
private static void CheckResultIsCompatibleWithCall(IReturn valueToReturn, ICallSpecification spec)
{
var requiredReturnType = spec.ReturnType();
if (!valueToReturn.CanBeAssignedTo(requiredReturnType))
{
throw new CouldNotSetReturnDueToTypeMismatchException(valueToReturn.TypeOrNull(), spec.GetMethodInfo());
}
}
}
}

Просмотреть файл

@ -13,5 +13,6 @@ namespace NSubstitute.Core
void InvokePerArgumentActions(CallInfo callInfo);
IEnumerable<ArgumentMatchInfo> NonMatchingArguments(ICall call);
MethodInfo GetMethodInfo();
Type ReturnType();
}
}

Просмотреть файл

@ -7,6 +7,8 @@ namespace NSubstitute.Core
public interface IReturn
{
object ReturnFor(CallInfo info);
Type TypeOrNull();
bool CanBeAssignedTo(Type t);
}
public class ReturnValue : IReturn
@ -14,6 +16,8 @@ namespace NSubstitute.Core
private readonly object _value;
public ReturnValue(object value) { _value = value; }
public object ReturnFor(CallInfo info) { return _value; }
public Type TypeOrNull() { return _value == null ? null : _value.GetType(); }
public bool CanBeAssignedTo(Type t) { return _value.IsCompatibleWith(t); }
}
public class ReturnValueFromFunc<T> : IReturn
@ -21,6 +25,9 @@ namespace NSubstitute.Core
private readonly Func<CallInfo, T> _funcToReturnValue;
public ReturnValueFromFunc(Func<CallInfo, T> funcToReturnValue) { _funcToReturnValue = funcToReturnValue ?? ReturnNull(); }
public object ReturnFor(CallInfo info) { return _funcToReturnValue(info); }
public Type TypeOrNull() { return typeof (T); }
public bool CanBeAssignedTo(Type t) { return typeof (T).IsAssignableFrom(t); }
private static Func<CallInfo, T> ReturnNull()
{
if (typeof(T).IsValueType) throw new CannotReturnNullForValueType(typeof(T));
@ -36,6 +43,8 @@ namespace NSubstitute.Core
_valuesToReturn = ValuesToReturn(values).GetEnumerator();
}
public object ReturnFor(CallInfo info) { return GetNext(); }
public Type TypeOrNull() { return typeof (T); }
public bool CanBeAssignedTo(Type t) { return typeof (T).IsAssignableFrom(t); }
private T GetNext()
{
@ -63,6 +72,8 @@ namespace NSubstitute.Core
_valuesToReturn = ValuesToReturn(funcs).GetEnumerator();
}
public object ReturnFor(CallInfo info) { return GetNext(info); }
public Type TypeOrNull() { return typeof (T); }
public bool CanBeAssignedTo(Type t) { return typeof (T).IsAssignableFrom(t); }
private T GetNext(CallInfo info)
{

Просмотреть файл

@ -46,7 +46,7 @@ namespace NSubstitute.Core
public ConfiguredCall LastCallShouldReturn(IReturn value, MatchArgs matchArgs)
{
if (_lastCallRouter.Value == null) throw new CouldNotSetReturnException();
if (_lastCallRouter.Value == null) throw new CouldNotSetReturnDueToNoLastCallException();
var configuredCall = _lastCallRouter.Value.LastCallShouldReturn(value, matchArgs);
ClearLastCallRouter();
return configuredCall;

Просмотреть файл

@ -1,16 +1,49 @@
using System.Runtime.Serialization;
using System;
using System.Reflection;
using System.Runtime.Serialization;
namespace NSubstitute.Exceptions
{
public class CouldNotSetReturnException : SubstituteException
public abstract class CouldNotSetReturnException : SubstituteException
{
const string WhatProbablyWentWrong =
"Could not find a call to return from.\n"+
"Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)).\n" +
protected const string WhatProbablyWentWrong =
"Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)),\n" +
"and that you are not configuring other substitutes within Returns() (for example, avoid this: mySub.SomeMethod().Returns(ConfigOtherSub())).\n" +
"\n" +
"If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member.\n" +
"Return values cannot be configured for non-virtual/non-abstract members.";
"Return values cannot be configured for non-virtual/non-abstract members.\n" +
"\n" +
"Correct use:\n" +
"\tmySub.SomeMethod().Returns(returnValue);\n" +
"\n" +
"Potentially problematic use:\n" +
"\tmySub.SomeMethod().Returns(ConfigOtherSub());\n" +
"Instead try:\n" +
"\tvar returnValue = ConfigOtherSub();\n" +
"\tmySub.SomeMethod().Returns(returnValue);\n" +
"";
public CouldNotSetReturnException() : base(WhatProbablyWentWrong) { }
protected CouldNotSetReturnException(string s) : base(s + "\n\n" + WhatProbablyWentWrong) { }
protected CouldNotSetReturnException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
public class CouldNotSetReturnDueToNoLastCallException : CouldNotSetReturnException
{
public CouldNotSetReturnDueToNoLastCallException() : base("Could not find a call to return from.") { }
protected CouldNotSetReturnDueToNoLastCallException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
public class CouldNotSetReturnDueToTypeMismatchException : CouldNotSetReturnException
{
public CouldNotSetReturnDueToTypeMismatchException(Type returnType, MethodInfo member) : base(DescribeProblem(returnType, member)) { }
private static string DescribeProblem(Type typeOfReturnValueOrNull, MethodInfo member)
{
return typeOfReturnValueOrNull == null
? String.Format("Can not return null for {0}.{1} (expected type {2}).", member.DeclaringType.Name, member.Name, member.ReturnType.Name)
: String.Format("Can not return value of type {0} for {1}.{2} (expected type {3}).", typeOfReturnValueOrNull.Name, member.DeclaringType.Name, member.Name, member.ReturnType.Name);
}
protected CouldNotSetReturnDueToTypeMismatchException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}